定位:入门 / 概念启蒙 适合:完全没接触过 Compose 的 Android 开发者,或从 View 体系转过来的同学
一、先讲一个故事:装修房间的两种方式
想象你要装修一个房间,有两种干活方式。
方式 A:命令式(imperative) 你拿着清单,一步步指挥工人:
- 在墙上钉一个挂钩。
- 如果衣架挂了 3 件衣服,就把挂钩换成大号。
- 如果衣服超过 10 件,再装一个衣柜。
每多一种状态,你都要手写「如果......就......」的逻辑。界面和状态是两条平行线 ,你必须手动保持它们同步。这就是传统 Android 的 View + XML 开发:findViewById、setText、setVisibility......状态一变,你就得记得去更新对应的控件。
方式 B:声明式(declarative) 你只写一句话:「衣架的数量 = 衣服数量 ÷ 5,衣柜的数量 = 衣服数量 ÷ 10」。工人(框架)自己会算,你从不手动搬砖。
这就是 Jetpack Compose。你只描述「界面长什么样」,框架负责「状态变化后如何更新」。这是两种思维方式的根本转变。
二、Compose 到底解决了什么痛点
| 痛点 | View + XML 时代 | Compose |
|---|---|---|
| 状态同步 | 手动 setText/setVisibility,容易漏更新 |
状态变了界面自动重组 |
| 代码分散 | XML 管布局、Kotlin 管逻辑、Adapter 管列表 | 一个 Kotlin 函数搞定全部 |
| 自定义控件 | 继承 View、写 onMeasure/onDraw,样板代码一堆 |
一个 @Composable 函数 |
| 列表复用 | RecyclerView + Adapter + ViewHolder | LazyColumn 一个函数 |
对小白来说,Compose 最大的好处是:学习路径短,写界面的心智负担小 。对资深开发来说,最大的好处是:消灭了 View 与数据之间那一整层容易出 bug 的胶水代码。
三、第一个 Compose 程序:Hello Compose
先感受一下长什么样。下面是一个完整可运行的最小例子:
kotlin
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.example.hellocompose.ui.theme.HelloComposeTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
HelloComposeTheme {
Surface {
Greeting("Compose")
}
}
}
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
HelloComposeTheme {
Greeting("Compose")
}
}
这段代码里有两个最关键的东西:
@Composable:标记一个函数是「描述界面」的函数。它没有返回值,因为它不是「返回一个界面」,而是「把界面画出来」。setContent { }:Activity 的入口,从传统的setContentView切换到这里,后面所有内容都在这个声明式世界里运行。
四、声明式 vs 命令式:同一个计数器,两种写法
这是本篇文章最重要的案例,请仔细对比。
命令式写法(传统 View)------你要手动做三步
kotlin
class CounterActivity : AppCompatActivity() {
private var count = 0
private lateinit var textView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_counter)
textView = findViewById(R.id.tv_count)
findViewById<Button>(R.id.btn_add).setOnClickListener {
count++
textView.text = "$count" // ① 必须手动同步
}
}
}
声明式写法(Compose)------只描述关系
kotlin
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) } // 状态
Column {
Text("当前计数:$count") // 界面
Button(onClick = { count++ }) { // 事件只改状态
Text("+1")
}
}
}
注意看:Compose 版本里没有一行「更新文本」的代码 。你只是说「文本显示 count」,然后按钮把 count 加一。框架发现 count 变了,自动重新执行 Counter,文本自己就变了。
这就是 Compose 的心智模型,一句话总结:
UI = f(state)。界面是状态的函数,状态一变,界面自动重算。
五、小白必知的 3 个名词(先混个脸熟)
- Composable(可组合函数) :用
@Composable标注、用来描述界面的函数。可以理解成「乐高积木块」。 - State(状态) :界面背后会变化的数据,比如计数器里的
count。状态变了,界面才变。 - Recomposition(重组):状态变化时,框架自动重新执行相关 Composable 的过程。这是 Compose 的发动机,下一篇专门讲。
六、资深开发者的醍醐灌顶时刻
如果你是从 View 体系过来的,请重点体会这三点:
- 没有
findViewById,也没有notifyDataSetChanged。UI 更新不再是一条条命令,而是一份「由状态推导出界面」的声明。这消灭了最常见的一类 bug:状态改了但界面忘了刷新。 - Composable 函数会被反复调用,而且是无序的。所以它必须是「纯函数」------同样的输入产生同样的输出,不能在里面写副作用(比如直接发网络请求)。这一点很多人一开始不理解,下一篇会深入。
- Compose 不是「换了个语法糖的 View」,它是编译器、运行时、UI 工具链协同的一套新体系。理解了「声明式」和「重组」,后面所有知识都顺理成章。
七、常见坑
- 把 Compose 当 View 用:还在想着「拿到这个控件引用去改它」。在 Compose 里没有控件引用,只有状态。改状态,界面自己变。
- 在 Composable 里做耗时操作:Composable 会反复执行,如果里面发网络请求,会重复请求 N 次。耗时操作要放到「副作用」里(第 8 篇讲)。
- 忘记
remember:下一篇会讲,现在只需记住,var count by remember { mutableStateOf(0) }里remember不能丢,丢了重组时状态会丢失。
八、动手题
把上面的计数器 demo 改造一下:加一个「-1」按钮,再显示「count 是偶数还是奇数」。改完自己跑一遍,体会「只改状态、不碰界面」的感觉。
九、学习资料
- 官方 Compose 文档:developer.android.com/jetpack/com...
- 官方《Thinking in Compose》:developer.android.com/develop/ui/...
- 官方 Compose 入门 Codelab:developer.android.com/courses/pat...
(下一篇预告:Composable 函数与「重组」机制,搞懂 Compose 的发动机。)