一、前言
已有项目 View 难以全部升级到 Compose 框架,所以考虑在传统 Android View 视图框架中接入 Compose 框架,也就是 View 和 Compose 混合开发。
接下来的内容是在有 Android 开发的基础之上去讲述的。
二、Composable 概念
方法加上@Composable
注解对应的就是View,也叫做Composable函数,compose中没有传统的View这个概念。
对于需要渲染成界面的Composable函数,也称之为可组合函数 :
- 此函数带有@Composable注解,表明他是一个可组合函数,所有组合函数都必须要带上这个注解
- 可组合函数需要在其他可组合函数的 作用域内 被调用
- 为了与普通函数区分,要求组合函数 首字母大写
- 带上 @Preview注解的Composeable函数,点击desgin选项,可以实时预览样式,不过函数不能带参数,不然预览不了
三、Compose和传统XML混合使用
ComposeView其实是个Android的View。
AndroidView其实是个Composable函数。
AndroidView和ComposeView是采用传统XML的View和Compose组件互相调用的桥梁。
1.XML中使用Compose
XML中写一个ComposeView组件

然后 setContent 一下就好了

2.Compose中使用XML
在Composeable 函数作用域中使用AndroidView

四、单一的Activity管理
Android Compose 中不使用Fragment,而是采用单一Activity管理多个可组合组件( Composable )的架构。
Compose通过 NavController 和 NavHost 实现页面导航,支持动态添加、移除可组合组件,无需传统Fragment即可实现界面切换。
这种架构优化了内存管理和界面渲染效率,避免了Fragment生命周期管理复杂问题。
构建视图的起点就是Activity,控件的使用方法就不细说了。

强制使用Fragment其实也可以,也就是前面说的在Android中使用Compose

五、Compose状态管理和数据流
1.重组(状态)
首先,要抛弃传统 Android View 的命令式思维(找到这个TextView,然后设置它的文本)。
Compose 是声明式的,你只需要声明在某种状态下,UI 应该长什么样。
当状态改变时,Compose 框架会自动重新执行 相关的 UI 代码(这个过程叫重组 Recomposition),从而更新界面。
为了让这个过程可预测、易于管理且不易出错,Compose 官方强烈推荐并践行单向数据流(Unidirectional Data Flow, UDF)架构。
- 状态提升(State Hoisting):将状态从子组件提升到共同的父组件,通过参数传递状态和回调函数,实现单向数据流
- remember 和 mutableStateOf:用于在重组过程中保持本地状态,是 Compose 状态管理的基础
交互引起的数据流变化会引起视图的重组。
重组范围是只有关联到的组件会重组。根组件都会重组。
① 基础重组

kotlin
@Composable
fun RecompositionBasic() {
var count by remember { mutableIntStateOf(0) }
println("RecompositionBasic 被调用 - count: $count") // 观察重组
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "点击次数: $count",
style = MaterialTheme.typography.headlineMedium
)
Button(
onClick = {
count++ // 状态改变触发重组
println("按钮被点击,count 变为: $count")
}
) {
Text("点击我")
}
// 这个组件不依赖 count,不应该重组
StaticComponent()
}
}
@Composable
fun StaticComponent() {
println("StaticComponent 被重组了") // 观察是否被重组
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
text = "我是静态组件,不依赖任何状态",
modifier = Modifier.padding(16.dp)
)
}
}
② 使用key能优化重组
key
的作用是为每个列表项提供一个稳定的标识符 。Compose 会基于这些稳定的 key 来追踪每个项的状态,并根据需要进行重组。这样,Compose 就可以在数据发生变化时,仅更新有变化的项,而不是重新绘制整个列表。优化视图重用,避免不必要的重组和视图创建。

javascript
// 使用 key 优化重组
LazyColumn {
items(
items = items,
key = { item -> item.id } // 使用稳定的 key
) { item ->
}
}
③ 智能重组
- 最小化重组范围:只重组发生变化的部分。
- 智能追踪视图状态:通过唯一的标识符(如 key)和数据绑定,Compose 可以智能地识别哪些视图需要更新,哪些可以保留不变。
scss
// 智能重组 - Compose 跳过不必要的重组
@Composable
fun SmartRecomposition() {
var parentState by remember { mutableStateOf(0) }
println("SmartRecomposition 父组件重组")
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(
text = "智能重组",
style = MaterialTheme.typography.headlineLarge
)
Button(onClick = { parentState++ }) {
Text("触发父组件状态变化: $parentState")
}
Spacer(modifier = Modifier.height(16.dp))
// 传递相同的参数,Compose 会跳过重组
ChildComponent(
title = "固定标题", // 稳定参数
value = 100 // 稳定参数
)
Spacer(modifier = Modifier.height(16.dp))
// 传递变化的参数,会触发重组
ChildComponent(
title = "动态标题",
value = parentState // 变化的参数
)
}
}
kotlin
@Composable
fun ChildComponent(
title: String,
value: Int
) {
println("ChildComponent 重组 - title: $title, value: $value")
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium
)
Text(
text = "值: $value",
style = MaterialTheme.typography.bodyMedium
)
}
}
}
2.数据流
核心思想:单向数据流 (UDF)
- 状态 向下流动,事件向上流动 (State flows down, events flow up)。
① 状态向下流动 (State Flows Down)
- 状态(State) :驱动 UI 的数据。
- 向下流动 :高级别的组件(通常是 ViewModel 或屏幕级别的 Composable 函数)持有并管理状态。它将这些状态作为参数,传递给需要展示这些数据的低级别的子 Composable 函数。
- 子 Composable 只负责接收 和展示状态,它自己不应该去修改这个状态。
② 事件向上流动 (Events Flow Up)
- 事件(Event) :用户的操作,比如点击按钮、输入文本等。
- 向上流动 :当用户在低级别的 Composable 上进行操作时,这个 Composable 不会直接修改状态。相反,它会触发一个回调函数,这个回调函数是由父组件(或者是 ViewModel)传递给它的。
- 这个事件沿着组件树向上传递,最终到达状态的持有者(ViewModel)。
③ 状态更新
- ViewModel 接收到事件后,根据业务逻辑更新它自己持有的 状态。
- 由于 ViewModel 中的状态(通常是 StateFlow 或 MutableState)发生了变化,Compose 会自动检测到这个变化。
- Compose 触发重组(Recomposition) ,所有读取了这个状态的 Composable 函数会重新执行。
- UI 根据新的状态值,自动刷新成最新的样子。
没看懂?不要着急!接下来会有个例子!
关于Flow,个人的一篇文章:juejin.cn/post/748117...
六、三个层次的架构模式
根据上面说的 Compose 的特点,可以得出三个层次的架构模式。
1.UI 层 (UI Layer)
①UI 元素 (Composable 函数)
也就是视图,用来显示数据(状态)
② 状态持有者 (State Holders)
持有并管理 UI 状态 (State)。向 UI 暴露状态 (通过 StateFlow)。提供处理 UI 事件(Events)的方法,并在此方法内执行业务逻辑或调用其他层来更新状态。
实现方法: ViewModel。
ViewModel 持有 StateFlow。
Composable 通过 collectAsStateWithLifecycle()
订阅这个 StateFlow,实现状态向下流动。
用户操作触发 Composable 中的事件回调,例如 onClick = { viewModel.refreshNews() },实现事件向上流动。
ViewModel 中的 refreshNews() 方法更新 UiState。
StateFlow 发出新的 UiState,Composable 自动重组并刷新界面。
注意的是这里的ViewModel 是分在了UI层面
2.领域层 (Domain Layer)
这一层是可选的。
装复杂的、可在多个 ViewModel 之间复用的业务逻辑 。它位于 UI 层和数据层之间,起到一个协调和解耦的作用。
这个具体看下面的例子吧。
3.数据层 (Data Layer)
这是应用所有数据的来源。
提供和管理应用所需的数据 。它应该作为单一数据源 (Single Source of Truth) 。
我们熟悉的仓库 (Repository) 就应该放在这里,这是数据层的门面。UI 层和领域层只与 Repository 交互,不关心数据究竟来自网络还是本地数据库。Repository 负责决定从哪里获取数据、何时缓存数据等策略。
七、总结 + 例子
三层架构:
- UI
- 领域
- 数据
目录结构应该是这样子的

activity
ui 也就是 composable 函数就不放出来了
kotlin
class DebugActivity : AppCompatActivity(){
// Compose 中 viewModel 的标准获取方式
private val viewModel: DebugViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
return setContent {
MaterialTheme{
// 有状态的控件
// 从 ViewModel 中收集状态
val items by viewModel.uiState.collectAsState()
DebugScreen(
items = items.items,
context = this.applicationContext,
// 将 UI 事件委托给 ViewModel
onUrlChanged = viewModel::onUrlChanged,
onGoClicked = viewModel::onGoClicked,
onDebugSwitchChanged = viewModel::onDebugSwitchChanged,
onBackPress = {
finish()
}
)
}
}
}
}
Viewmodel
kotlin
class DebugViewModel(application: Application) : AndroidViewModel(application) {
// 引入新的状态来表示加载中、成功、失败
data class DebugScreenUiState(
val isLoading: Boolean = true,
var items: List<ListItem> = emptyList(),
val error: String? = null
)
// 数据来源
private val getDebugScreenDataUseCase by lazy {
GetDebugScreenDataUseCase()
}
// 私有的、可变的 StateFlow,作为内部状态持有者 加下划线区别开私有
private val _uiState = MutableStateFlow(DebugScreenUiState())
// asStateFlow 确保 单向数据流 和 封装 特性
// !!!注意重绘的触发逻辑!!!
val uiState: StateFlow<DebugScreenUiState> = _uiState.asStateFlow()
init {
// ViewModel 初始化时加载初始数据
loadInitialData()
}
private fun loadInitialData() {
getDebugScreenDataUseCase(context)
.onSuccess {
_uiState.value = DebugScreenUiState(isLoading = false, items = it)
}
.onFailure {
_uiState.value = DebugScreenUiState(isLoading = false, error = it.message)
}
}
fun onUrlChanged(newUrl: String) {
}
fun onGoClicked(url: String) {
}
fun onDebugSwitchChanged(isEnabled: Boolean) {
}
}
领域层
kotlin
class GetDebugScreenDataUseCase{
// 这里相当于中转站
// 可以持有多个Repository
private val userRepository by lazy {
UserRepository()
}
operator fun invoke(context: Context): Result<List<ListItem>> {
}
}