Jetpack Compose 核心机制:Composable与Modifier

一、从命令式到声明式

传统Android开发时代,我们都是这样写界面的:在XML里定义布局,在Activity里使用 findViewById 找到控件,然后调用 setXXXX 方法去更新UI。这就是命令式 写法,由开发者去告诉系统"怎么做"。

Jetpack Compose 则完全不同。我们不再需要告诉系统"怎么做 ",只需要告诉系统UI"长什么样 "。当数据状态变化时,界面会自动更新 。这就是声明式UI

今天我们就来认识 Compose 最核心的两个概念:ComposableModifier

二、Composable------一切皆函数

在 Compose 中,所有的UI组件都是带有 @Composable 注解的 kotlin 函数。在上一篇我们展示了Android Studio 自动生成的默认代码MainActivity.kt 里的一个Composable:

kotlin 复制代码
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    Text(
        text = "Hello $name!",
        modifier = modifier
    )
}

//我们还可以将它改为最简单的一个Composable
@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

该函数描述了这样的一个UI效果:一段显示问候语的文字。

Compose的运行可以划分两个阶段:

  1. 组合(Composition) :首次渲染时,UI框架执行所有 @Composable 函数,生成一棵运行时的UI树。
  2. 重组(Recomposition):当状态发生变化时,框架重新执行受影响的Composable函数,但只更新变化的部分。

Compose 非常智能------它不会因为一个状态变化就把整个界面全部重绘一遍,而是只重组那些读取了变化状态的Composable。这正是Compose高效的原因。

下面请看一个例子,演示了Composable如何与状态配合工作:

kotlin 复制代码
import androidx.compose.runtime.*
import androidx.compose.material3.*
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun Counter() {
    // 可观察状态
    var count by remember { mutableStateOf(0) }
    
    Column {
        Text(text = "点击次数:$count", style = MaterialTheme.typography.headlineSmall)
        Button(onClick = { count++ }) {
            Text("点我")
        }
    }
}

@Preview
@Composable
fun PreviewCounter() {
    Counter()
}

点击按钮,count 变化,只有 Text 所在的Composable会被重组,Button 本身不会被重新执行。

Compose

三、Modifier------修饰UI的利器

如果说Composable定义了UI"是什么",那么 Modifier 就定义了UI"长什么样、如何响应"。

Modifier 可以做到四类事情(常用的Modifier):

  • 尺寸与布局:padding()fillMaxWidth()size()
  • 外观样式:background()border()
  • 用户输入:clickable()draggable()
  • 交互行为:scrollable()semantics()

Modifier 最强大的地方在于链式调用------可以把多个修饰符串在一起:

kotlin 复制代码
@Composable
fun UserCard(name: String) {
    Column(
        modifier = Modifier
            .padding(16.dp)           // 外间距
            .fillMaxWidth()           // 撑满宽度
            .background(Color.LightGray, shape = RoundedCornerShape(8.dp))
            .clickable { /* 点击处理 */ }
            .padding(12.dp)           // 内间距
    ) {
        Text(text = name)
    }
}

💡最佳实践

任何一个 Composable 函数都应该接受一个 modifier 参数,并将其传递给第一个实际发出UI的子组件。这样可以使得组件具备良好的复用性。

四、Modifier的顺序

这是初学者最容易踩的坑:Modifier的顺序会影响最终结果

原因在于,每个修饰符函数都会对前一个函数返回的 Modifier 进行包装。后面的修饰符包裹前面的修饰符,就像一层层洋葱皮。

下面看一个典型案例:

kotlin 复制代码
Column(
    modifier = Modifier
        .clickable { /* 点击 */ }
        .padding(16.dp)
)

clickable在前,padding在后,那么实际可点击区域是整个区域(包括了padding区域)。

但是如果反过来写:

kotlin 复制代码
Column(
    modifier = Modifier
        .padding(16.dp)
        .clickable { /* 点击 */ }
)

padding产生的空白区域就不可点击了,只有内容区域能响应点击。

我们在写Modifier时,要时刻想清楚"谁包裹谁":

  • 若要点击区域更大,clickable在前,padding在后。
  • 若只想内容区域可点击,padding在前,clickable在后。

五、总结

理解 Compose,抓住两个核心就够了:

  • Composable 是用 @Composable 注解的函数,描述 UI 是什么。状态变化时,只有受影响的 Composable 会被重组,保证高效。
  • Modifier 是修饰 UI 的工具,通过链式调用调整尺寸、样式、行为和交互。顺序决定效果,写的时候要多加留意。

掌握了这两个概念,你就已经迈进了 Jetpack Compose 的大门。剩下的,就是在实践中不断熟悉各种 Composable 和 Modifier 的用法了。

六、参考资料