23、Jetpack Compose入门:Compose声明式UI思想、可组合函数、布局与修饰符、状态管理与重组、Compose与传统View混合开发

说实话,第一次接触Compose的时候,我心里是有点抵触的。毕竟在Android圈子里摸爬滚打了好几年,XML写布局早就成了肌肉记忆。但当我真正花了一个周末把一个小模块用Compose重写之后,嗯,真香。今天我们就来聊聊这个改变Android UI开发方式的东西。

声明式UI思想:不是“怎么画”,而是“是什么”

传统的View体系,说白了是命令式的。你要告诉系统:先创建一个TextView,再设置它的文字,然后把它放到LinearLayout里,最后设置一下边距。每一步都是指令,像在指挥一个机器人干活。

Compose不一样。它是声明式的。你只需要描述UI“应该长什么样”,剩下的交给框架。举个例子:

// 传统方式
val textView = TextView(context)
textView.text = "Hello"
textView.setTextColor(Color.RED)
layout.addView(textView)

// Compose方式
Text("Hello", color = Color.Red)

看到区别了吗?Compose里没有“创建”、“添加”这些动作。你直接说“这里有个红色的Hello文字”,它就出来了。我在项目中遇到过很多同事刚开始写Compose时,总想着去拿View的引用做操作,其实完全不需要。

核心思想:UI = f(state)。界面是状态的函数。状态变了,界面自动更新。你不用手动去findViewById,也不用操心什么时候该刷新。

可组合函数:最小的UI积木

在Compose里,一切UI都是函数。用@Composable注解标记的函数,就叫可组合函数。你可以把它理解成乐高积木,一块一块拼起来。

@Composable
fun Greeting(name: String) {
    Text(text = "你好,$name")
}

@Composable
fun MyScreen() {
    Column {
        Greeting("小明")
        Greeting("小红")
    }
}

我个人习惯把可组合函数分成三类:

  • 叶子组件:只包含基础组件,比如Text、Image、Button
  • 容器组件:负责布局,比如Column、Row、Box
  • 业务组件:组合了数据和UI,比如用户卡片、商品列表

你想想看,这种分层方式是不是比传统View的继承体系清爽多了?没有复杂的基类,没有抽象工厂,就是函数调函数。

布局与修饰符:给UI加点“料”

Compose的布局组件不多,但够用。Column(纵向排列)、Row(横向排列)、Box(层叠排列),这三个基本能覆盖90%的场景。

真正让Compose灵活的是修饰符(Modifier)。它就像流水线,一个接一个地给组件添加属性。

@Composable
fun StyledCard() {
    Card(
        modifier = Modifier
            .padding(16.dp)
            .fillMaxWidth()
            .clickable { /* 点击事件 */ }
            .shadow(4.dp, shape = RoundedCornerShape(8.dp))
    ) {
        Text("点我试试", modifier = Modifier.padding(8.dp))
    }
}

修饰符的顺序很重要。我曾经踩过一个坑:把.clickable放在了.padding前面,结果点击区域还是整个屏幕宽度,因为padding是在点击之后才生效的。记住,修饰符是从左到右、从外到内依次应用的。

小技巧:如果你需要给多个组件加相同的修饰符,可以提取成一个变量。比如 val commonModifier = Modifier.padding(8.dp).fillMaxWidth(),然后到处复用。

状态管理与重组:Compose的“心脏”

Compose最神奇的地方在于:状态变了,UI自动更新。这背后靠的是“重组”(Recomposition)。

简单来说,当某个状态发生变化时,Compose会重新执行依赖这个状态的可组合函数,只更新变化的部分,不会全量刷新。

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }

    Button(onClick = { count++ }) {
        Text("点击了 $count 次")
    }
}

这里remember的作用是让状态在重组时不被重置。mutableStateOf创建了一个可观察的状态。当count变化时,只有Text会重组,Button不会。

为什么会这样?因为Compose在编译期就分析出了哪些代码块依赖了count。这种细粒度的重组,性能比传统的notifyDataSetChanged好太多了。

注意:不要在可组合函数里做耗时操作或IO操作。重组可能会被频繁触发,导致性能问题。我见过有人直接在Composable里调网络请求,结果界面卡成PPT。

Compose与传统View混合开发:不是二选一

很多项目不可能一夜之间全换成Compose。好在Google考虑到了这一点,Compose可以和传统View共存。

在XML布局里嵌入Compose:

// 在Activity或Fragment中
setContentView(R.layout.activity_main)

// 在XML中
<androidx.compose.ui.platform.ComposeView
    android:id="@+id/compose_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

// 在代码中
findViewById<ComposeView>(R.id.compose_view).setContent {
    MyComposable()
}

反过来,在Compose里使用传统View:

@Composable
fun AndroidViewExample() {
    AndroidView(
        factory = { context ->
            TextView(context).apply {
                text = "我是传统TextView"
                textSize = 18f
            }
        },
        modifier = Modifier.fillMaxWidth()
    )
}

我个人建议的策略是:新功能用Compose写,旧功能逐步迁移。不要一上来就重构整个项目,风险太大。我曾经在一个中型项目里尝试全量迁移,结果光是兼容旧的自定义View就花了两周。

知识体系总览

下面这张图帮你理清Compose的核心脉络:

Jetpack Compose 声明式UI思想 @Composable 函数 布局与修饰符 状态管理与重组 与传统View混合 UI = f(state) 自动刷新 叶子 / 容器 / 业务 Column / Row / Box Modifier 链式调用 remember + mutableStateOf 重组(Recomposition) ComposeView / AndroidView Compose 核心知识体系

总结一下

Compose不是要取代传统View,而是给了我们另一种选择。它的声明式思想让UI代码更直观,可组合函数让复用变得简单,修饰符链式调用比XML属性更灵活,状态管理机制则彻底解放了开发者。

如果你刚开始学,我建议从一个小模块入手,比如写一个登录页面或者用户信息卡片。先感受一下“状态变了UI自动更新”的爽快感,再慢慢深入。别急着把所有东西都塞进Compose,循序渐进才是正道。

一句话总结:Compose让Android UI开发从“怎么画”变成了“是什么”,从“手动刷新”变成了“自动重组”。这不仅仅是工具的变化,更是思维方式的升级。


公众号:蓝海资料掘金营,微信deep3321