Jetpack Compose 核心概念
mediumCompose声明式UI重组StateSide Effect
声明式 vs 命令式 UI
传统 Android View 系统是命令式的:你持有 View 的引用,然后调用方法改变它的状态:
// 命令式(传统 View)
textView.text = "Hello"
button.isEnabled = false
progressBar.visibility = View.VISIBLE
Compose 是声明式的:你描述在某个状态下 UI 应该是什么样子,框架负责映射为实际 UI:
// 声明式(Compose)
@Composable
fun Counter(count: Int, onIncrement: () -> Unit) {
Column {
Text(text = "Count: $count")
Button(onClick = onIncrement) {
Text("Increment")
}
}
}
// 当 count 变化时,Compose 自动重组 UI,不需要你手动调用 textView.text = ...
重组(Recomposition)
Compose 的核心执行机制:当 @Composable 函数依赖的状态发生变化时,Compose 会重新调用该函数,生成新的 UI 树与旧的对比,只更新改变的部分。
@Composable
fun LoginScreen() {
var username by remember { mutableStateOf("") } // 状态
var password by remember { mutableStateOf("") }
Column {
// username 变化时,只有这一部分重组(Compose 足够智能)
TextField(
value = username,
onValueChange = { username = it },
label = { Text("用户名") }
)
TextField(
value = password,
onValueChange = { password = it },
visualTransformation = PasswordVisualTransformation(),
label = { Text("密码") }
)
Button(
onClick = { /* 登录 */ },
enabled = username.isNotEmpty() && password.isNotEmpty()
) {
Text("登录")
}
}
}
重组的关键特性:
- 幂等性:相同输入必须产生相同 UI(不能有随机性或副作用)
- 快速:Compose 只重组实际改变的部分,不是整个界面
- 可跳过:如果参数没变,
@Composable可以跳过重组(@Stable标记帮助 Compose 判断)
State 管理
remember 与 mutableStateOf
// remember:在重组间保持状态(否则每次重组都重置为初始值)
// mutableStateOf:创建可观察状态,变化时触发重组
var count by remember { mutableStateOf(0) }
// ❌ 不用 remember:每次重组 count 重置为 0
var count = mutableStateOf(0)
状态提升(State Hoisting)
把状态从子 Composable 移到父 Composable,使子组件成为无状态(纯展示):
// ❌ 状态内聚在组件内(难以复用、难以测试)
@Composable
fun CounterButton() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
}
// ✓ 状态提升:组件无状态,由调用方控制
@Composable
fun CounterButton(count: Int, onIncrement: () -> Unit) {
Button(onClick = onIncrement) {
Text("Count: $count")
}
}
// 父组件持有状态
@Composable
fun CounterScreen() {
var count by remember { mutableStateOf(0) }
CounterButton(count = count, onIncrement = { count++ })
}
ViewModel + StateFlow(推荐)
@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {
private val _count = MutableStateFlow(0)
val count = _count.asStateFlow()
fun increment() { _count.value++ }
}
@Composable
fun CounterScreen(viewModel: MainViewModel = hiltViewModel()) {
val count by viewModel.count.collectAsStateWithLifecycle()
CounterButton(count = count, onIncrement = viewModel::increment)
}
Side Effects(副作用)
Composable 函数应该是纯函数,但实际开发中需要执行副作用(网络请求、日志、监听等)。Compose 提供了专门的 API:
@Composable
fun UserScreen(userId: String) {
val viewModel: UserViewModel = hiltViewModel()
// LaunchedEffect:在 Composable 进入组合时启动协程,key 变化时重新启动
LaunchedEffect(userId) {
viewModel.loadUser(userId) // userId 变化时重新加载
}
// DisposableEffect:需要清理的副作用(类似 onDestroy)
DisposableEffect(Unit) {
val listener = SomeEventListener { /* handle */ }
SomeEventBus.register(listener)
onDispose {
SomeEventBus.unregister(listener) // 离开 Composition 时清理
}
}
// SideEffect:每次重组后都执行(用于同步外部状态)
SideEffect {
analytics.setUserId(userId)
}
val user by viewModel.user.collectAsStateWithLifecycle()
UserContent(user = user)
}
常用布局与 Modifier
// 基础布局(类比 LinearLayout)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text("标题", style = MaterialTheme.typography.headlineMedium)
Text("内容", color = MaterialTheme.colorScheme.onSurface)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Icon(Icons.Default.Person, contentDescription = null)
Text("用户名")
Text("在线", color = Color.Green)
}
// Box(叠加,类比 FrameLayout)
Box(modifier = Modifier.size(100.dp)) {
Image(painter = rememberAsyncImagePainter(url), contentDescription = null)
// 角标叠加在图片右上角
Badge(
modifier = Modifier.align(Alignment.TopEnd)
) { Text("3") }
}
// 列表(类比 RecyclerView,虚拟化渲染)
LazyColumn {
items(userList) { user ->
UserItem(user = user)
}
}
Compose vs View 系统
| 对比项 | View 系统 | Compose |
|---|---|---|
| 代码量 | 多(XML + Kotlin) | 少(纯 Kotlin) |
| 学习曲线 | 中等 | 较陡(需要理解重组、状态) |
| 性能 | 成熟稳定 | 持续改进,复杂列表稍差 |
| 互操作 | - | AndroidView 嵌入 View,ComposeView 嵌入 Compose |
| 动画 | Animator 手动控制 | animate*AsState、AnimatedVisibility 声明式 |
| 测试 | Espresso |
ComposeTestRule |
建议:新项目优先用 Compose。旧项目可以逐模块迁移,Compose 与传统 View 可以共存。