16、Compose基础:Compose思想、@Composable、Modifier、状态管理、重组、实战:声明式UI入门
各位同学,今天我们来聊聊Jetpack Compose。说实话,我第一次接触Compose时,内心是抗拒的。毕竟XML写了好多年,突然让我用Kotlin写UI,总觉得怪怪的。但真正用起来之后,我只想说一句——真香。
这一章,我会带你从零开始理解Compose的核心思想。不扯虚的,全是实战干货。
Compose思想:从命令式到声明式
传统的Android UI开发,是典型的命令式。你告诉系统:先创建TextView,再设置文字,然后添加到布局里。每一步都是指令。
Compose不一样。它是声明式的。你只需要描述UI应该长什么样,剩下的交给框架。
核心区别:
- 命令式:如何做(How)
- 声明式:做什么(What)
举个例子。你要显示一个用户名字。传统写法你得findViewById,然后setText。Compose呢?直接写一个Text组件,把数据传进去就行。数据变了,UI自动更新。
我个人习惯把Compose理解成「UI即函数」。输入数据,输出界面。没有中间商赚差价。
我的经验:刚开始学Compose时,别想着把老项目全改成Compose。先在一个小页面试试水。我当初就是在个人中心页面开始用的,改完发现代码量直接砍了一半。
@Composable:一切UI的起点
在Compose里,所有UI组件都是函数。但普通函数不行,得加上@Composable注解。
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
这个注解告诉编译器:嘿,这个函数要参与重组。说白了,Compose会跟踪这个函数的执行,当依赖的数据变化时,重新调用它。
你可能会问:那是不是所有UI都得加这个注解?嗯,基本上是的。但有个例外——如果你只是调用别的Composable函数,自己不需要直接操作UI,可以不加。不过实践中,我建议每个UI组件都加上,保持一致性。
注意:Composable函数不能直接写在普通函数里调用。必须在另一个Composable函数的作用域内。我曾经犯过这个错,编译直接报错,排查了半天。
Modifier:布局的瑞士军刀
Modifier是Compose里最灵活的概念。它相当于传统XML里的layout_xxx属性,但更强大。
你可以用Modifier控制大小、边距、点击事件、背景、动画……几乎所有UI属性。
@Composable
fun StyledButton() {
Button(
onClick = { /* 点击事件 */ },
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.height(48.dp)
.background(Color.Blue)
) {
Text("点击我")
}
}
Modifier的链式调用很优雅。每个方法返回一个新的Modifier,叠加效果。顺序很重要!先padding再background,和先background再padding,效果完全不同。
避坑指南:我曾经在项目里把Modifier的顺序搞反了,导致按钮的点击区域和视觉区域不一致。用户点空白区域也能触发点击,排查了好久才发现是Modifier顺序的问题。记住:先布局修饰,再视觉修饰。
状态管理:UI的灵魂
Compose的UI是数据的投影。数据变了,UI自动更新。这个数据就是状态。
最简单的状态管理用remember和mutableStateOf。
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("点击次数: $count")
}
}
remember保证状态在重组时不被重置。mutableStateOf创建可观察的状态。当count变化时,Compose自动重组UI。
但注意,remember只在当前Composable的生命周期内有效。如果Activity重建,状态就丢了。想持久化?用rememberSaveable。
状态提升:当多个组件需要共享状态时,把状态提到它们的公共父组件里。这是Compose的推荐做法。我见过不少新手把状态放在子组件里,结果兄弟组件之间传值传得乱七八糟。
重组:高效更新的秘密
重组是Compose最核心的机制。当状态变化时,Compose不会重新执行整个UI树,而是只更新受影响的部分。
怎么做到的?Compose会跟踪每个Composable函数读取了哪些状态。只有读取了变化状态的函数才会被重新调用。
举个例子:
@Composable
fun UserProfile(user: User) {
Column {
Text(user.name) // 只依赖user.name
Text(user.email) // 只依赖user.email
}
}
如果只改了user.name,只有第一个Text会重组。第二个Text不受影响。
性能陷阱:别在Composable函数里做耗时操作。重组可能很频繁,每次重组都执行耗时操作,UI会卡顿。我见过有人直接在Composable里做网络请求,结果页面一刷新就卡死。记住:Composable只负责UI,数据逻辑放ViewModel里。
实战:声明式UI入门
光说不练假把式。我们来写一个简单的用户列表页面。
@Composable
fun UserListScreen() {
val users = remember {
listOf(
User("张三", "zhangsan@example.com"),
User("李四", "lisi@example.com"),
User("王五", "wangwu@example.com")
)
}
LazyColumn {
items(users) { user ->
UserCard(user)
}
}
}
@Composable
fun UserCard(user: User) {
Card(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
) {
Row(
modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
// 头像占位
Box(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(Color.Gray)
)
Spacer(modifier = Modifier.width(12.dp))
Column {
Text(text = user.name, fontWeight = FontWeight.Bold)
Text(text = user.email, color = Color.Gray)
}
}
}
}
data class User(val name: String, val email: String)
这段代码实现了什么?一个可滚动的用户列表,每个卡片显示头像、姓名和邮箱。全部用声明式写法,没有XML,没有Adapter,没有ViewHolder。
你想想看,如果用传统方式,你得写一个RecyclerView,一个Adapter,一个ViewHolder,一个item布局文件。现在呢?一个Composable函数搞定。
我的建议:刚开始写Compose时,先模仿。照着官方示例敲一遍,理解每个组件的用法。然后试着改一改,比如把列表改成网格,或者加个搜索框。动手是最好的学习方式。
知识体系总览
下面这张图总结了本章的核心知识点。你可以把它当作学习地图。
这张图把Compose的核心概念串起来了。声明式思想是基础,@Composable是工具,Modifier是画笔,状态管理是数据源,重组是引擎。五者缺一不可。
好了,这一章的内容就到这里。Compose的世界很大,但入门其实不难。记住一句话:把UI当成函数的输出,数据变了,UI自然就变了。剩下的,就是多写多练。
公众号:蓝海资料掘金营,微信deep3321