11、Compose入门:Jetpack Compose基础、声明式UI、Compose与View互操作
说实话,第一次接触Jetpack Compose的时候,我心里是有点抵触的。毕竟在Android上写XML布局写了快十年,突然让我用Kotlin代码直接写UI,总觉得哪里不对。但真正上手之后,我发现这东西确实有点东西——它不仅仅是换了个写法,而是彻底改变了我们构建UI的思维方式。
声明式UI:和传统方式说再见
传统的Android UI开发,我们用的是命令式的方式。说白了就是:你先在XML里画好布局,然后在Activity里找到这些View,再手动设置数据、监听器。这个过程就像是在指挥一个木偶——你拉一下线,它动一下。
而Compose采用的是声明式UI。你只需要告诉框架「我想要什么」,剩下的交给框架去处理。举个例子:
// 传统方式
TextView textView = findViewById(R.id.text_view);
textView.setText("Hello");
textView.setOnClickListener(v -> { ... });
// Compose方式
@Composable
fun Greeting(name: String) {
Text(
text = "Hello $name",
modifier = Modifier.clickable { ... }
)
}
看到区别了吗?Compose里没有「找到View」这一步,也没有「设置文本」这一步。我们直接描述UI应该长什么样,数据变了UI自动更新。我在项目中遇到过很多次因为忘记更新UI导致的bug,用Compose之后这类问题少了很多。
核心思想:UI是状态的函数。状态变了,UI自动重建。你不需要手动管理UI的更新。
Compose基础:从Hello World开始
写一个Compose界面其实很简单。你只需要一个@Composable注解的函数,然后在里面调用其他Composable函数就行。就像搭积木一样。
@Composable
fun MyScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "欢迎来到Compose世界",
fontSize = 24.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = { /* 点击事件 */ }) {
Text("点击我")
}
}
}
嗯,这里要注意几个点:
- Modifier:这是Compose里最核心的概念之一。所有布局、样式、交互都通过它来配置。我个人习惯把Modifier放在参数的第一位,这样代码更清晰。
- Column/Row/Box:这是Compose的三大布局组件,分别对应垂直排列、水平排列和层叠布局。
- Spacer:用来占位,相当于传统布局里的
Space或margin。
小技巧:刚开始写Compose时,你可能会觉得Modifier链式调用很别扭。我的建议是——先别管那么多,照着写就行。用不了两天你就会发现这玩意儿比XML的layout_xxx属性好用多了。
状态管理:让UI活起来
Compose的UI是「死」的,只有加上状态管理,它才能「活」起来。最常用的状态管理方式就是remember和mutableStateOf。
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "计数: $count",
fontSize = 20.sp
)
Button(onClick = { count++ }) {
Text("增加")
}
}
}
这里有个关键点:remember保证了状态在重组时不会被重置,mutableStateOf则让Compose知道这个状态变了需要重新绘制UI。我曾经见过有人忘记用remember,结果每次点击按钮UI都不更新——嗯,那场面挺尴尬的。
避坑指南:千万不要在Composable函数里直接修改普通变量!比如var count = 0然后count++,这样UI是不会更新的。必须用mutableStateOf或者StateFlow这类可观察的数据容器。
Compose与View互操作:新老共存
现实项目中,你不可能一下子把所有页面都改成Compose。大多数团队都是渐进式迁移。这时候就需要Compose和传统View能互相嵌套。
在Compose中使用Android View
用AndroidView这个Composable函数,你可以把任何传统View嵌入到Compose布局中:
@Composable
fun MapViewComposable() {
AndroidView(
factory = { context ->
MapView(context).apply {
// 配置MapView
}
},
update = { mapView ->
// 更新MapView
}
)
}
我在项目中用这个方式集成了百度地图SDK——因为百度地图当时还没有Compose版本。效果还不错,就是要注意生命周期管理。
在传统View中使用Compose
反过来,你也可以在XML布局里放一个ComposeView:
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
然后在Activity或Fragment里设置Compose内容:
findViewById<ComposeView>(R.id.compose_view).setContent {
MyComposableFunction()
}
说实话,这种互操作能力是Compose能快速推广的重要原因。你不需要「All in」,可以慢慢来。
知识体系总览
下面这张图概括了本章的核心内容,我建议你保存下来,学完整个章节后再回来看一遍,会有更深的理解:
Compose的布局系统
Compose的布局和传统View的布局不太一样。它用的是「测量-布局-绘制」三步走,但整个过程是递归的。你想想看,每个Composable函数都会告诉父组件「我有多大」,父组件再根据所有子组件的大小决定怎么摆放。
| 传统View | Compose |
|---|---|
| XML布局文件 | Kotlin代码直接描述 |
| findViewById | 不需要,直接引用 |
| 手动更新UI | 状态变化自动更新 |
| LayoutParams | Modifier配置 |
| 生命周期依赖Activity | Composition生命周期 |
我个人觉得,Compose最大的优势不是「少写代码」,而是「逻辑更集中」。以前一个按钮的点击事件、样式、数据绑定可能分散在XML、Activity、ViewModel三个地方。现在全在一个Composable函数里,一眼就能看明白。
我的建议:刚开始学Compose,别急着上复杂项目。先拿一个简单的页面练手,比如登录页、个人中心页。等你习惯了「声明式」的思维方式,再回头看传统View,你会觉得那玩意儿怎么那么啰嗦。
好了,这一章的内容就到这里。记住:Compose不是银弹,但它确实是Android UI开发的未来。早点上手,早点受益。