Compose简介:Jetpack Compose基础,声明式UI,与View系统的对比
先聊聊我为什么「叛变」到Compose
说实话,两三年前我还是个「View派」的死忠。那时候我觉得XML写布局挺直观的,findViewById虽然烦但够稳。直到我被拉进一个重度依赖列表滑动、频繁更新UI的项目——嗯,正是那个号称「离线优先」的收银台应用。
那时候我们用的是RecyclerView+DiffUtil,每次数据变化都要手动通知适配器更新。页面稍微复杂一点,逻辑就散落在Activity、Fragment、Adapter里,找Bug像在翻垃圾桶。后来我试着把几个核心界面用Compose重写,结果代码量少了将近一半,而且再也不用担心「更新了某个字段但忘记notifyDataSetChanged」这种蠢问题了。
所以这一章,我想跟你聊聊Jetpack Compose到底是什么,它凭什么取代View系统,以及我们在这套离线优先架构课里为什么非得用它。
声明式UI——说白了就是「描述界面现在长什么样」
传统的View系统是「命令式UI」。你写代码告诉系统「先创建这个TextView,再设置它宽高,然后给它设文字,最后加到父布局里」。每一步都是命令,系统吭哧吭哧执行。
而Compose是声明式UI。你只需要说:「这个按钮长这样,文字是它,颜色是它」。界面状态变化时,Compose自动重新调用你的可组合函数,计算出新的界面。
命令式 = 你告诉系统「怎么做」
声明式 = 你告诉系统「要什么」,系统自己搞定「怎么做」
举个例子。假设按钮的文字需要根据网络状态切换:
// View 时代的做法
val button = findViewById<Button>(R.id.status_button)
if (isOnline) {
button.text = "在线"
button.isEnabled = true
} else {
button.text = "离线"
button.isEnabled = false
}
// 如果isOnline变了,你得再写一遍这个逻辑,或者用监听器
// Compose 的做法
@Composable
fun StatusButton(isOnline: Boolean) {
Button(
onClick = { /* 点击事件 */ },
enabled = isOnline
) {
Text(if (isOnline) "在线" else "离线")
}
}
// isOnline一旦改变,Compose自动重新运行这个函数,按钮就地更新
你想想看,后者是不是清爽多了?我在项目里最爽的就是再也不用写那些「if else加手动刷新」的模板代码了。而且Compose的更新是「智能」的——它只刷新变化的部分,性能上不输给手写的DiffUtil。
跟View系统比,到底强在哪?
我直接拉个表格,你一眼就明白:
| 对比维度 | 传统 View 系统 | Jetpack Compose |
|---|---|---|
| UI构建方式 | XML+代码,命令式 | Kotlin代码,声明式 |
| 状态管理 | 手动监听/通知/刷新 | 自动观察重组(Recomposition) |
| 代码量 | 较多,模板代码多 | 约减少40%-60% |
| 布局性能 | 通常够用,但复杂列表需DiffUtil | 自动跳过不变部分,零成本重组 |
| 可测试性 | 依赖Instrumentation测试,慢 | 纯函数式,单元测试就好,快 |
| 版本兼容 | 推荐AppCompat,但兼容仍麻烦 | Compose BOM统一管理,向后兼容好 |
| 学习曲线 | 中等,XML+生命周期需要理解 | 需要理解「重组」「副作用」「状态提升」 |
我个人习惯是,新项目直接上Compose,老项目有余力就慢慢迁移。你看我们这门课的主题是「离线优先架构」,离线场景下UI状态本来就复杂(网络状态、缓存数据、同步冲突),如果用View系统,状态流转要写一大坨代码。而Compose天然跟State、Flow这些响应式数据流配合得极好,简直就是为离线而生的UI框架。
比如你的项目重度依赖NDK绘制(游戏、自定义相机预览),或者已经用Jetpack Navigation的纯Fragment导航写了三年,贸然混用容易出坑。我曾经在一个老项目里试图把复杂Camera2预览层用Compose包起来,结果TextureView跟Compose的测量逻辑打架,搞了三天才修好。所以迁移要策略性推进,别一股脑全换了。
Compose基础——五分钟上手
写Compose说白了就是写一堆「可组合函数」。这些函数用 @Composable 注解标记,没有返回值,只有界面描述。
一个最简单的例子:
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
你把它放到Activity里就能用:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Greeting("离线架构实战")
}
}
}
是不是很简单?嗯,这里要注意:setContent 就是Compose世界的入口。整个页面就是一棵由@Composable函数组成的树。
核心基石:状态、重组、副作用
Compose里面有三个概念至关重要,我把它们总结为「Compose三剑客」:
- 状态(State)——UI读取的「活数据」。
- 重组(Recomposition)——状态变化时,Compose重新运行相关函数。
- 副作用(Side Effect)——在重组之外做额外事情(比如网络请求、存数据库)。
拿我们离线优先的场景举例。比如你有个界面显示「上次同步时间」:
@Composable
fun SyncStatusView() {
var lastSyncTime by remember { mutableStateOf("从未同步") }
// 模拟从Repository获取最新同步时间
LaunchedEffect(Unit) {
lastSyncTime = repository.getLastSyncTime()
}
Text("上次同步:$lastSyncTime")
}
remember 让状态在重组时不被重置。mutableStateOf 观察这个值,一变就触发重组。LaunchedEffect 是副作用,只在第一次进入组合时执行一次网络/数据库读取。
我曾经在线上遇到过一个Bug——离线消息列表刷不出来。排查了很久发现是
remember用错了:我把从Room查出来的Flow数据直接collect到了remember里,结果每次重组都重新收集Flow,导致无限循环。正确的做法是用collectAsState()收集Flow,或者用LaunchedEffect在合适的作用域里收集。记住:运行时状态用
mutableStateOf,数据流状态用 collectAsState。
布局体系:告别ConstraintLayout嵌套
Compose的布局是「组合式」的,常用布局只有几个:Column、Row、Box、LazyColumn。没有measure/layout回调,没有LayoutParams,没有weight和gravity绕来绕去。写出来的代码跟设计稿长得几乎一样。
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text("离线缓存列表", fontSize = 20.sp)
Spacer(Modifier.height(8.dp))
LazyColumn {
items(cachedItems) { item ->
CacheItemRow(item)
}
}
}
你看,没有Adapter,没有ViewHolder,没有onCreateViewHolder。一个LazyColumn加items就搞定了列表。性能上它会自动做差分更新,比手搓DiffUtil开发效率高得多。
Compose与离线优先架构的「天作之合」
我们这门课选Compose不是跟风,而是它的特性太贴合离线场景了:
- 状态即UI——离线状态下,数据状态(本地缓存/网络错误/同步中)天然就是UI状态,直接用
State表示,不需要额外桥接。 - 响应式更新——后台同步线程把数据写入Room后,
Flow自动发射新数据,Compose自动刷新列表。你的UI层几乎不需要处理「同步完成后的界面刷新」逻辑。 - 纯函数式可组合——离线业务逻辑(比如冲突合并、缓存过期判断)可以抽取成纯Kotlin函数,跟Compose的UI代码完美解耦,单元测试非常容易。
下面这张图把Compose在离线优先架构中的位置和关系画出来了,你可以先有个整体印象:
从图上你能看到:Compose UI层通过State/Flow直接跟ViewModel通信,ViewModel背后是Repository封装好的离线策略(本地缓存优先、网络同步、冲突合并)。整个箭头是单向的——数据向下流,UI向上反映状态变化。这就是Compose最适合离线架构的原因:状态变化天然驱动UI更新,你完全不用写「手动刷新」的胶水代码。
小结:这次Compose入门,你先记住三件事
- Compose是声明式UI,你只描述「要什么」,系统自己搞定「怎么做」。
- 核心三个词:State、Recomposition、SideEffect。弄懂它们,Compose就算入门了。
- 跟View比,Compose更擅长处理复杂状态驱动的UI——离线场景正是这种。