83、Jetpack Compose入门:Composable函数、布局、状态管理、Modifier
说实话,Compose 刚出来那会儿,我内心是抗拒的。毕竟 XML 写布局写了七八年,肌肉记忆都形成了。但真正上手写了两个页面后,我不得不承认——这玩意儿确实香。今天咱们就把 Compose 的四个核心概念掰开揉碎聊一聊。
一、Composable 函数:一切皆函数
在 Compose 里,UI 不再是 XML 文件,而是 Kotlin 函数。你写一个函数,加上 @Composable 注解,它就能生成 UI。听起来很玄乎?其实没那么复杂。
@Composable
fun Greeting(name: String) {
Text(text = "你好,$name")
}
这个函数没有返回值,它直接描述 UI。我刚开始写的时候总想 return 一个 View,后来才明白——Composable 函数是“描述”UI,而不是“创建”UI。系统会在合适的时机调用它,生成或更新界面。
关键点:Composable 函数只能从另一个 Composable 函数中调用。你不能在普通 Kotlin 函数里直接调用它。
嗯,这里要注意:函数名建议用名词,比如 UserProfile、ProductCard,而不是 showUserProfile。这是 Compose 社区的约定,说白了就是把它当成 UI 组件来命名。
二、布局:Column、Row、Box 三板斧
XML 时代我们有 LinearLayout、RelativeLayout、FrameLayout。Compose 里对应的就是 Column、Row、Box。我个人习惯把它们叫做“布局三兄弟”。
| Compose 布局 | 对应 XML | 排列方向 |
|---|---|---|
| Column | LinearLayout(vertical) | 垂直排列 |
| Row | LinearLayout(horizontal) | 水平排列 |
| Box | FrameLayout | 层叠排列 |
@Composable
fun SimpleLayout() {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("第一行")
Text("第二行")
Row {
Text("左")
Text("右")
}
}
}
我在项目中遇到过一个问题:Column 里嵌套 Row,Row 里的内容死活不居中。后来发现是没给 Row 设置 Modifier.fillMaxWidth()。你想想看,Row 默认只包裹内容,宽度不够自然没法居中。
小技巧:布局嵌套不要太深。超过三层嵌套,建议抽成独立的 Composable 函数。这样代码清晰,性能也好。
三、状态管理:remember 与 mutableStateOf
状态管理是 Compose 的灵魂。没有状态,UI 就是死的。Compose 的状态管理核心就两个东西:remember 和 mutableStateOf。
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("点击了 $count 次")
}
}
这段代码里,mutableStateOf(0) 创建了一个可观察的状态,remember 保证重组时状态不被重置。当 count 变化时,Compose 会自动重组用到这个状态的 UI 部分。
我曾经踩过一个坑:把状态定义在了 Composable 函数外面,结果页面旋转后状态丢了。后来才搞明白——remember 是跟 Composable 的生命周期绑定的。如果想让状态在配置变更后依然存活,得用 rememberSaveable。
注意:不要在 Composable 函数里直接写 var count = 0。这样每次重组都会重置为 0,按钮永远显示 0 次。嗯,我刚开始就犯过这个错。
四、Modifier:链式调用的魔法
Modifier 是 Compose 里最灵活的部分。它用链式调用的方式,给组件添加布局、样式、事件等能力。说白了,它就是 Compose 版的 LayoutParams + 样式 + 监听器。
@Composable
fun StyledText() {
Text(
text = "Hello Compose",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(Color.LightGray)
.clickable { /* 点击事件 */ }
.border(2.dp, Color.Blue)
)
}
Modifier 的顺序很重要。你想想看,先 padding 再 background,和先 background 再 padding,效果完全不一样。前者是内边距后背景,后者是背景后内边距。我建议你记住一个原则:先布局,后样式,再事件。
常用 Modifier 速查:
Modifier.fillMaxWidth()/fillMaxHeight()— 填充父容器Modifier.padding()— 内边距Modifier.background()— 背景色/背景图Modifier.clickable()— 点击事件Modifier.border()— 边框Modifier.size()— 固定尺寸
五、知识体系总览
下面这张图把今天讲的内容串起来了。你可以看到,Composable 函数是基础,布局和 Modifier 负责 UI 呈现,状态管理负责数据驱动。三者缺一不可。
六、实战:组合起来写个登录界面
光说不练假把式。咱们把上面四个概念组合起来,写一个简单的登录界面。
@Composable
fun LoginScreen() {
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var isLoggedIn by remember { mutableStateOf(false) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(24.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
if (isLoggedIn) {
Text("登录成功!欢迎 $username")
} else {
OutlinedTextField(
value = username,
onValueChange = { username = it },
label = { Text("用户名") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(12.dp))
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("密码") },
visualTransformation = PasswordVisualTransformation(),
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = { if (username.isNotBlank() && password.isNotBlank()) isLoggedIn = true },
modifier = Modifier.fillMaxWidth()
) {
Text("登录")
}
}
}
}
这段代码里,我们用 remember 管理了三个状态,用 Column 做垂直布局,用 Modifier 设置了填充、内边距等样式。当用户点击登录按钮时,isLoggedIn 变为 true,UI 自动切换到登录成功页面。
个人建议:刚开始学 Compose,别急着用复杂的状态管理工具(比如 ViewModel + StateFlow)。先用 remember 和 mutableStateOf 把基础搞明白。等遇到跨组件共享状态的需求时,再引入 ViewModel 也不迟。
好了,这一章的内容就到这里。Compose 的学习曲线其实比 XML 平缓,只要你理解了“函数描述 UI”这个核心思想,后面学起来会越来越顺。