Compose 基础入门:从命令式到声明式的思维转变
说实话,我第一次接触 Compose 的时候,内心是抗拒的。做了七八年 Android 开发,XML 加 findViewById 那套流程早就刻在肌肉记忆里了。但当我真正花了一周时间写完第一个 Compose 页面后,我不得不承认——回不去了。
这一章,我们就来聊聊 Compose 最核心的几个概念。我会结合自己踩过的坑,帮你把基础打扎实。
声明式 UI 与命令式 UI 的对比
先问一个问题:你平时是怎么更新 UI 的?
传统方式大概是这样的:
// 命令式:告诉系统每一步怎么做
TextView textView = findViewById(R.id.my_text);
textView.setText("新内容");
textView.setTextColor(Color.RED);
textView.setVisibility(View.VISIBLE);
这就是典型的命令式 UI。你像个项目经理一样,事无巨细地指挥每个控件该干什么。页面复杂了,这种代码会变得非常难维护。我在一个老项目里见过一个 Activity 写了 3000 行,光 findViewById 就占了 100 多行。
而 Compose 的声明式写法是这样的:
@Composable
fun MyText(content: String) {
Text(
text = content,
color = Color.Red,
modifier = Modifier.padding(8.dp)
)
}
你只需要描述 UI 应该长什么样,剩下的交给框架。说白了,你告诉 Compose:「我要一个红色的文字,内容是这个」,至于它怎么创建、怎么更新,你不用操心。
核心区别一句话总结:
命令式 = 你指挥系统干活
声明式 = 你描述想要的结果
我个人习惯把声明式 UI 想象成「拍照片」。你摆好姿势(状态),相机自动成像(UI 渲染)。状态变了,重新拍一张就行。而命令式 UI 就像画画——每一笔都要你亲手画。
@Composable 函数与重组
Composable 函数是 Compose 的基石。它有几个特点:
- 用
@Composable注解标记 - 不能直接调用,只能在另一个 Composable 函数中调用
- 没有返回值,只负责描述 UI
来看个例子:
@Composable
fun Greeting(name: String) {
Text(text = "你好,$name")
}
@Composable
fun MyScreen() {
Column {
Greeting("张三")
Greeting("李四")
}
}
这里有个关键概念叫重组。当 name 参数变化时,Compose 不会重新执行整个 MyScreen,而是只重组 Greeting 这个函数。这就是 Compose 高效的原因——它只更新变化的部分。
小技巧:重组是智能的,但不是免费的。我在项目中遇到过一个问题:在 Composable 函数里做了大量计算,导致每次重组都卡顿。后来把计算移到了 remember 里,流畅多了。后面会讲。
Modifier 链式调用
Modifier 是 Compose 里用来装饰组件的工具。它采用链式调用的方式,非常优雅。
@Composable
fun StyledButton() {
Button(
onClick = { /* 点击事件 */ },
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.height(48.dp)
.background(Color.Blue)
.clip(RoundedCornerShape(8.dp))
) {
Text("点击我")
}
}
你想想看,如果用传统方式实现同样的效果,得写多少行 XML?而且 Modifier 的顺序很重要。我曾经犯过一个错误:把 clip 写在 background 前面,结果圆角没生效。因为 Modifier 是从左到右依次执行的,顺序错了效果就变了。
注意:Modifier 的顺序会影响最终效果。一般建议:先布局(padding、size),再装饰(background、border),最后裁剪(clip)。
Compose 中的状态管理
状态管理是 Compose 的灵魂。没有状态,UI 就是死的。
mutableStateOf:状态的基础
mutableStateOf 创建一个可观察的状态。当值变化时,Compose 会自动重组依赖它的 UI。
@Composable
fun Counter() {
val count = mutableStateOf(0)
Button(onClick = { count.value++ }) {
Text("点击次数:${count.value}")
}
}
等等,这样写有问题。每次重组都会重新创建 count,状态就丢了。这时候就需要 remember 出场了。
remember:记住状态
remember 的作用是让状态在重组时保持不变。它只在首次组合时执行,后续重组会跳过。
@Composable
fun Counter() {
val count = remember { mutableStateOf(0) }
Button(onClick = { count.value++ }) {
Text("点击次数:${count.value}")
}
}
这样写就对了。count 的值会一直保留,直到 Composable 从组合树中移除。
经验之谈:我刚开始用 Compose 时,经常忘记加 remember。结果每次点击按钮,数字刚变成 1 又跳回 0。排查了半天才发现是状态没记住。嗯,这种坑踩一次就记住了。
状态提升:让状态共享
如果两个组件需要共享同一个状态,就需要把状态提升到它们的公共父组件中。
@Composable
fun Parent() {
val text = remember { mutableStateOf("") }
ChildA(text)
ChildB(text)
}
@Composable
fun ChildA(text: MutableState<String>) {
TextField(value = text.value, onValueChange = { text.value = it })
}
@Composable
fun ChildB(text: MutableState<String>) {
Text("你输入的是:${text.value}")
}
这就是状态提升。数据向下流动,事件向上传递。说白了,谁用数据谁就持有状态,多个组件共享就提到父组件去。
知识体系总览
下面这张图帮你理清本章的核心脉络:
写在最后
这一章的内容其实就四个核心点:声明式思维、Composable 函数、Modifier 用法、状态管理。看起来不多,但每一个都是 Compose 的基石。我个人建议你动手写几个小例子,比如计数器、待办列表,把 remember 和 mutableStateOf 用熟。代码写多了,这些概念自然就内化了。
记住一句话:Compose 不是让你写更少的代码,而是让你写更清晰的代码。状态变了,UI 自动跟着变——这才是现代 UI 框架该有的样子。
公众号:蓝海资料掘金营,微信deep3321