3、Compose基础入门:@Composable注解、声明式UI模型、Modifier基础、布局基础

好,我们直接进入正题。这一章咱们把Compose的骨架搭起来。你可能看过一些教程,上来就讲各种组件怎么用。但我个人的习惯是,先搞懂底层那几条核心规则。规则明白了,后面怎么写都顺手。

Compose和传统的View系统,说白了是两套完全不同的思想。你如果带着旧思路来学,会很别扭。我们先看最根本的东西——@Composable。

3.1 @Composable:你写的每一个“组件”都是函数

在Compose里,没有类继承,没有LayoutInflater,没有findViewById。你写界面,就是在写函数。

核心认知:每一个带@Composable注解的函数,都是一个UI单元。它会告诉编译器:“这个函数能转化为界面的一部分。”

举个例子:

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

就这么简单。Greeting函数本身没有任何特殊之处,除了那个@Composable注解。这个注解就像是给函数贴了个标签,Compose的编译器看到它,就会去生成管理UI状态的底层代码。

我在项目中遇到过同事问:“为什么我的Composable函数不能从普通函数里调用?”

嗯,这个问题很典型。原因是@Composable函数只能在另一个@Composable函数的作用域里被调用。它依赖一个特殊的“Composition上下文”,普通函数没有这个上下文。你想想看,如果能在任意地方调用UI函数,那状态管理和重组就乱套了。

警告:不要试着在@Composable函数里执行耗时操作或挂起函数。Composable函数可能被频繁调用(重组),任何阻塞操作都会导致UI卡顿。

3.2 声明式UI模型:你描述目标,框架负责实现

传统View系统是“命令式”的:你告诉系统每一步怎么做——先创建TextView,找到它,再setText。Compose是“声明式”的:你直接描述界面“应该长什么样”。

为什么这样更好?

因为“描述”天然就是幂等的。你传什么数据,界面就渲染成什么样。数据变了,Compose会“智能地”重新执行那些受影响的Composable函数,这叫重组(Recomposition)。

我曾经为一个复杂的多状态页面调试过很久。旧代码里到处都是if-else判断可见性、更新文本。后来用Compose重写,一个when表达式把三种状态描述清楚,问题全消失了。说白了,你把精力花在定义“是什么”,而不是“怎么做”。

技巧:刚开始写Compose时,容易不自觉地去想“怎么更新这个TextView”。忘记它。你只需要想:我的数据是什么?界面应该根据这些数据长什么样子?剩下的交给Compose。

3.3 Modifier基础:布局和装饰的“万能工具包”

Modifier是Compose里最常用的东西。你可以把它想象成一个构建器,用来给组件添加尺寸、边距、背景、点击事件等等。

关键点:Modifier是链式调用的,顺序很重要。

@Composable
fun ModifierDemo() {
    Box(
        modifier = Modifier
            .size(100.dp)          // 先设尺寸
            .padding(16.dp)        // 再加上内边距(这会缩小可用区域)
            .background(Color.Blue) // 最后给剩余区域上色
    )
}

我个人的习惯是,把size或fillMaxSize这类“布局约束”放在最前面,然后才是padding、background这些“装饰”。

为什么顺序重要?

因为Modifier链的执行是从外到内的。一个常见的坑是:

// 错误顺序
Box(
    modifier = Modifier
        .background(Color.Red)
        .padding(16.dp)
)
// 实际效果:先画红色背景,然后在红色区域里切出16dp的内边距区域

我曾经就因为顺序搞反,调了半天UI对不齐。你一定记着:background、border这类“绘制”操作,尽量放在padding、size之后,否则它们会被覆盖掉。

常用Modifier速查表:

Modifier作用示例
size()固定宽高.size(48.dp)
fillMaxSize()尽可能填满父容器.fillMaxSize(0.5f)(一半父容器)
padding()内边距.padding(horizontal = 16.dp)
background()填充背景色.background(Color.Blue, shape = RoundedCornerShape(8.dp))
clickable()添加点击交互.clickable { /* 处理点击 */ }
border()添加边框.border(1.dp, Color.Gray)

3.4 布局基础:Column、Row、Box

有了@Composable和Modifier,我们还得把组件合理地摆放在屏幕上。Compose提供了三个最基础的布局容器,几乎能组合出任何界面。

3.4.1 Column:纵向排列

Column让你的子组件从上到下依次排列。可以指定垂直对齐方式(Arrangement)和水平对齐方式(Alignment)。

@Composable
fun ColumnDemo() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        verticalArrangement = Arrangement.SpaceEvenly, // 子项均匀分布
        horizontalAlignment = Alignment.CenterHorizontally // 水平居中
    ) {
        Text("第一行")
        Text("第二行")
        Button(onClick = { /* ... */ }) {
            Text("点击")
        }
    }
}

3.4.2 Row:横向排列

Row和Column刚好相反,子组件从左到右排列。用法完全对称。

@Composable
fun RowDemo() {
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically
    ) {
        Icon(Icons.Default.Favorite, contentDescription = null)
        Text("标题")
        Switch(checked = true, onCheckedChange = {})
    }
}

3.4.3 Box:层叠布局

Box最大的特点是子组件可以重叠。默认从左上角开始堆叠,通过Modifier.align()可以精确控制每个子组件的位置。

@Composable
fun BoxDemo() {
    Box(
        modifier = Modifier
            .size(200.dp)
            .background(Color.LightGray)
    ) {
        // 第一个子组件:铺满整个Box
        Text(
            text = "背景文字",
            modifier = Modifier.align(Alignment.Center)
        )
        // 第二个子组件:放在右上角
        Icon(
            imageVector = Icons.Default.Close,
            contentDescription = null,
            modifier = Modifier
                .align(Alignment.TopEnd)
                .padding(8.dp)
        )
    }
}

我在做一个浮层提示时,就是用Box实现的:底层放内容,上层悬浮一个半透明提示框,用Align.BottomCenter定位。Box真的是处理“层叠”场景的利器。

实践建议:界面布局90%用Column和Row就能搞定。只有需要子组件重叠、或者定位时,才考虑用Box。不要滥用。

好了,到这里,我们已经把Compose最核心的四块拼图都讲清楚了。下面这张图可以帮你理清它们的关系:

Compose基础入门:核心概念与关系 @Composable 入口 声明式UI模型 描述目标状态,框架自动渲染 Modifier 链式调用,定义尺寸、装饰、交互 Column / Row 线性排列:纵向 / 横向 Box 层叠布局:子组件重叠 / 定位 核心要点 1. 所有UI单元都是 @Composable 函数,只在Composable作用域内调用。 2. 你描述界面“是什么”,Compose自动处理“怎么做”。 3. Modifier链顺序影响最终效果:约束优先,装饰在后。 4. 90%场景用Column/Row,叠层或定位用Box。

你看,整个Compose的界面搭建逻辑其实很简单:用@Composable声明函数,函数内部用Column、Row、Box来摆放内容,每个元素通过Modifier链调整外观和交互。这就是声明式的力量——你不再操纵具体的View对象,而是通过函数组合来描述界面。

下一章,我们会进入状态管理。但在此之前,我建议你亲自动手写一个简单的界面:比如一个用户卡片,包含头像、姓名、简介,用Column和Row组合出来。试几次,你就找到感觉了。

易犯错误:学习初期容易把所有布局都塞进一个Column里。遇到复杂布局时,记得拆解成多个小的@Composable函数,每个函数只负责一个逻辑块。组合优于继承,这在Compose里是铁律。