本文深入剖析 Compose 的四个核心概念,帮你从「会用」进阶到「理解」。配合 Compose 从 0 到 1 阅读效果最佳。
@Composable —— UI 就是函数
作用
@Composable 是 Compose 的灵魂注解。被它标记的函数就是一个 UI 组件,可以直接在别的函数里调用。
核心规则
| 规则 | 说明 |
|---|
| 函数名首字母大写 | 约定俗成,与普通函数区分(如 MyButton 而非 myButton) |
| 只能被 @Composable 函数调用 | 普通函数不能直接调用 Composable 函数 |
| 没有返回值 | Composable 函数通常返回 Unit(描述 UI,不返回对象) |
| 可接收任意参数 | 通过参数控制 UI 外观和行为,实现复用 |
| 默认没有顺序 | 调用顺序不代表实际排列顺序(需要在布局容器中才有顺序) |
示例
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Composable fun MyTitle() { Text(text = "我是标题") }
@Composable fun MyPage() { MyTitle() MyTitle() MyTitle() }
|
带参数的可复用组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @Composable fun GreetingCard(name: String, isVip: Boolean = false) { Row( modifier = Modifier .fillMaxWidth() .padding(16.dp) .background( if (isVip) Color(0xFFFFD700) else Color.White, RoundedCornerShape(8.dp) ) .padding(12.dp) ) { Text( text = "欢迎,$name", fontWeight = if (isVip) FontWeight.Bold else FontWeight.Normal ) if (isVip) { Text("👑", modifier = Modifier.padding(start = 8.dp)) } } }
@Composable fun UserList() { Column { GreetingCard("张三") GreetingCard("李四", isVip = true) GreetingCard("王五") } }
|
适用场景
- 一切 UI 相关的函数:控件、布局、页面、弹窗、标题栏等
⚠️ 注意
- Composable 函数可能被频繁调用(重组时),不要在内部做耗时操作
- 副作用(网络请求、数据库读写)应使用
LaunchedEffect / SideEffect 等 - 函数内部创建的局部变量,每次重组都会重新创建
- 命名规范:Composable 函数首字母大写,参数顺序按重要性排列
Modifier —— 所有外观全靠它
作用
Modifier 是 Compose 的外观装饰系统,控制控件的尺寸、内边距、背景、圆角、边框、点击、滚动等一切外观和行为。所有控件都可以通过 modifier 参数接收 Modifier。
核心概念
链式调用
1 2 3 4 5
| Modifier .size(100.dp) // 最外层:固定 100x100 .background(Blue) // 第二层:蓝色背景 .padding(16.dp) // 第三层:内边距 16dp .clip(CircleShape) // 最内层:圆形裁剪
|
Modifier 是从外到内依次包裹的。上面这段代码的效果:最外限定 100x100 区域 → 铺蓝底 → 在蓝色内部再留 16dp 边距 → 最终裁剪成圆形。
顺序决定效果
1 2 3 4 5 6 7 8 9
| Modifier .background(Color.Red) .padding(16.dp)
Modifier .padding(16.dp) .background(Color.Red)
|
| 顺序 | 效果 |
|---|
.background(...).padding(...) | 背景在 padding 之外,背景区域更大 |
.padding(...).background(...) | 背景在 padding 之内,背景区域更小 |
常用 Modifier 分类速查
尺寸类
| Modifier | 说明 |
|---|
size(width, height) | 固定宽高 |
width(dp) / height(dp) | 固定宽度 / 高度 |
fillMaxWidth() / fillMaxHeight() | 填满父容器宽度 / 高度 |
fillMaxSize() | 宽高都填满 |
wrapContentWidth() / wrapContentHeight() | 包裹内容宽度 / 高度 |
defaultMinSize(minWidth, minHeight) | 最小尺寸约束 |
sizeIn(minWidth, maxWidth, minHeight, maxHeight) | 尺寸范围约束 |
内边距类
| Modifier | 说明 |
|---|
padding(all) | 四边相同 |
padding(horizontal, vertical) | 水平和垂直 |
padding(start, top, end, bottom) | 分别设置 |
外观类
| Modifier | 说明 |
|---|
background(color) | 纯色背景 |
background(color, shape) | 带形状的背景 |
clip(shape) | 裁剪形状 |
border(width, color) | 边框 |
border(width, color, shape) | 带形状的边框 |
shadow(elevation, shape) | 阴影 |
alpha(fraction) | 透明度 |
rotate(degrees) | 旋转 |
scale(scaleX, scaleY) | 缩放 |
offset(x, y) | 偏移(不影响布局) |
交互类
| Modifier | 说明 |
|---|
clickable { } | 点击 |
combinedClickable(onClick, onLongClick) | 点击 + 长按 |
布局类
| Modifier | 说明 |
|---|
weight(fraction) | 权重分配(Row / Column 内) |
align(alignment) | 对齐(Box 内) |
综合示例
1 2 3 4 5 6 7 8 9 10 11
| Box( modifier = Modifier .size(100.dp) .clip(RoundedCornerShape(12.dp)) .background(Color.Blue) .border(2.dp, Color.White, RoundedCornerShape(12.dp)) .clickable { }, contentAlignment = Alignment.Center ) { Text("点我", color = Color.White) }
|
适用场景
- 几乎所有控件的外观控制:设置大小、加边距、上颜色、加边框、添加交互
⚠️ 注意
- 顺序很重要:从外到内依次应用,不同顺序效果不同
- 同一个 Modifier 只能使用一次:不能写两个
.padding(),需要用参数组合 - 自定义 Modifier:通过扩展函数封装复用逻辑
- 可组合:
Modifier 是不可变的,每个 .xxx() 返回一个新的 Modifier 对象
状态(State)—— 数据变了,界面自动刷新
作用
State 是 Compose 最核心的心智模型:你只管改数据,UI 自动刷新。不需要 findViewById、setText、notifyDataSetChanged。
核心 API
mutableStateOf —— 创建可观察变量
1 2 3 4 5 6 7 8 9 10 11
| @Composable fun Counter() { var count by remember { mutableStateOf(0) }
Column { Text(text = "当前计数:$count") Button(onClick = { count++ }) { Text("点我 +1") } } }
|
| 关键字 | 作用 |
|---|
mutableStateOf(0) | 创建一个”可观察”的变量,值变了会自动通知界面刷新 |
remember { } | 让变量在界面重组时”记住”当前值,不会被重置 |
by | Kotlin 委托语法,让你直接写 count++ 而不是 count.value++ |
mutableStateOf 的三种写法
1 2 3 4 5 6 7 8 9 10 11
| var count by remember { mutableStateOf(0) } count++
val count = remember { mutableStateOf(0) } count.value++
val (value, setValue) = remember { mutableStateOf(0) } setValue(value + 1)
|
常见状态类型
| 状态 | 写法 | 示例 |
|---|
| 基本类型 | mutableStateOf(0) | 计数器、开关状态 |
| 字符串 | mutableStateOf("") | 输入框内容 |
| 布尔值 | mutableStateOf(false) | 展开/折叠、选中状态 |
| 列表 | mutableStateListOf<T>() | 待办列表、聊天记录 |
| 集合 | mutableStateMapOf<K, V>() | 键值对状态 |
| 对象 | mutableStateOf(MyData()) | 表单数据 |
| 可空类型 | mutableStateOf<T?>(null) | 可选数据 |
列表状态示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| @Composable fun TodoList() { val todoList = remember { mutableStateListOf<String>() } var inputText by remember { mutableStateOf("") }
Column { Row { TextField( value = inputText, onValueChange = { inputText = it }, modifier = Modifier.weight(1f) ) Button(onClick = { if (inputText.isNotBlank()) { todoList.add(inputText) inputText = "" } }) { Text("添加") } } LazyColumn { items(todoList.size) { index -> Text( text = "${index + 1}. ${todoList[index]}", modifier = Modifier.padding(8.dp) ) } } } }
|
remember 的进阶用法
remember(key) —— 依赖 key 变化重新计算
1 2 3 4 5 6 7 8
| @Composable fun UserProfile(userId: String) { val userData = remember(userId) { loadUserData(userId) } Text("用户名:${userData.name}") }
|
rememberSaveable —— 屏幕旋转后保留
1 2 3 4 5
| var temp by remember { mutableStateOf("") }
var saved by rememberSaveable { mutableStateOf("") }
|
remember 生命周期
1 2 3 4 5 6 7 8 9 10
| 首次进入 Composable → remember {} 执行,创建状态 ↓ 用户交互,状态变化 → 触发重组(Recomposition) → remember 返回已有值,不重新创建 ↓ 从界面移除(不再显示) → 状态被销毁 → 下次显示时重新创建
|
适用场景
- 任何需要「数据变化 → UI 自动更新」的场景:表单、列表、动画、主题切换等
⚠️ 注意
- 必须用
remember 包裹:var count = mutableStateOf(0)(缺少 remember)每次重组都会重置 - 状态提升(State Hoisting):将状态提升到父组件,通过参数传递,实现单向数据流
mutableStateListOf 和 mutableStateMapOf 自带可观察能力,内部元素变化会自动刷新- 避免过度使用:不是所有变量都需要 state,只有 UI 相关的才需要
重组(Recomposition)—— 谁变了就刷新谁
作用
当状态(State)变化时,Compose 会智能地只重绘受影响的控件,没有变化的部分原地不动。这是 Compose 高性能的核心机制。
工作原理
1 2 3 4 5 6 7 8 9
| State 变化 ↓ Compose 标记「受影响的 Composable 函数」 ↓ 执行 Recomposition:重新调用被标记的函数 ↓ 对比新旧 UI,仅更新变化的部分 ↓ 不受影响的函数 → 直接跳过,零开销
|
直观理解
1 2 3
| count 变了 → 只有 Text("当前计数:$count") 会重新渲染 → Button 不动 → 页面上 100 个其他控件也不动
|
重组的特点
| 特性 | 说明 |
|---|
| 选择性 | 只重组读取了变化状态的函数 |
| 乐观 | 任何状态变化都可能触发重组,但 Compose 会跳过无变化的部分 |
| 可中断 | 新的状态变化可以打断正在进行的重组 |
| 并行 | 多个 Composable 可以并行执行重组 |
| 幂等 | 同一输入多次重组结果相同(不应该有副作用) |
避免不必要的重组
使用不可变数据
1 2 3 4 5
| data class User(var name: String, var age: Int)
data class User(val name: String, val age: Int)
|
使用 derivedStateOf 派生状态
1 2 3 4 5 6 7 8 9 10 11 12
| @Composable fun FilteredList(items: List<String>, query: String) { val filtered = items.filter { it.contains(query) }
val filtered by remember { derivedStateOf { items.filter { it.contains(query) } } }
LazyColumn { items(filtered) { item -> Text(item) } } }
|
3. 状态读取延迟
1 2 3 4 5 6 7 8 9 10 11
| @Composable fun HeavyItem(onClick: () -> Unit, isSelected: Boolean) { Box(modifier = Modifier.clickable(onClick = onClick)) { if (isSelected) { Border() } } }
|
1 2 3 4 5 6 7 8
| @Composable fun HeavyItem(onClick: () -> Unit, isSelected: Boolean) { Box(modifier = Modifier.clickable(onClick = onClick)) { SelectionBorder(isSelected) } }
|
SideEffect —— 在重组中安全执行副作用
1 2 3 4 5 6 7 8 9 10
| @Composable fun AnalyticsTracker(pageName: String) {
LaunchedEffect(pageName) { logPageView(pageName) } }
|
| 副作用 API | 使用场景 |
|---|
LaunchedEffect(key) | 协程操作,key 变化时重新启动 |
SideEffect | 每次重组后执行(同步) |
DisposableEffect(key) | 需要清理的副作用(如注册/注销监听) |
rememberCoroutineScope() | 获取 Composable 生命周期内的协程作用域 |
适用场景
- 理解 Compose 性能、排查 UI 不刷新 bug、优化复杂列表
⚠️ 注意
- Composable 函数应该幂等:同样的输入,无论调用多少次结果相同
- 不要在 Composable 中直接做副作用:使用 Effect API
- 不要依赖 Composable 函数的调用顺序和次数:Compose 可能会跳过、重复调用
- 不要读取或修改全局变量:使用 State 代替
- 使用 Layout Inspector 或 Compose 编译器报告的「restartable」「skippable」标记来排查性能问题