布局基础(上):Column、Row、Box布局组件、Modifier修饰符详解、尺寸与约束、对齐方式
说实话,Jetpack Compose 的布局系统,是我从传统 View 体系转过来时最不适应、但后来觉得最爽的部分。传统 Android 里,你写个 LinearLayout 要配一堆 weight、gravity、orientation,嵌套多了性能还容易崩。Compose 呢?三个核心组件——Column、Row、Box——搞定绝大多数场景。今天我们就把这几个家伙掰开揉碎讲清楚。
一、三大布局组件:Column、Row、Box
这三个组件,说白了就是 Compose 里的「线性布局」和「帧布局」。但它们的用法比传统 View 灵活得多。
1. Column:垂直排列
Column 把子组件从上往下排。我刚开始用的时候,总觉得它就是个垂直的 LinearLayout,后来发现它配合 Modifier 能玩出很多花样。
@Composable
fun ColumnExample() {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF0F4FF)),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceEvenly
) {
Text("第一行", fontSize = 20.sp)
Text("第二行", fontSize = 20.sp)
Text("第三行", fontSize = 20.sp)
}
}
这里有个坑:verticalArrangement 控制的是垂直方向上的间距分布,horizontalAlignment 控制的是水平方向上的对齐。我见过不少新手把这两个搞反,结果布局乱成一团。
2. Row:水平排列
Row 和 Column 是对称的——一个横着排,一个竖着排。参数也几乎一样,只是方向不同。
@Composable
fun RowExample() {
Row(
modifier = Modifier
.fillMaxWidth()
.height(60.dp)
.background(Color(0xFFFFF3E0)),
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically
) {
Text("左", fontSize = 18.sp)
Text("中", fontSize = 18.sp)
Text("右", fontSize = 18.sp)
}
}
嗯,这里要注意:Row 的 verticalAlignment 默认是 Alignment.Top,如果你不指定,子组件会贴顶部对齐。我早期做搜索栏时吃过这个亏——图标和文字死活对不齐,后来才发现是默认对齐方式的问题。
3. Box:层叠布局
Box 是 Compose 里的「万能容器」。它允许子组件在 Z 轴上层叠,类似传统 View 的 FrameLayout。但比 FrameLayout 强大得多——你可以精确控制每个子组件的位置。
@Composable
fun BoxExample() {
Box(
modifier = Modifier
.size(200.dp)
.background(Color(0xFFE8F5E9)),
contentAlignment = Alignment.Center
) {
// 背景层
Box(
modifier = Modifier
.size(180.dp)
.background(Color(0xFFC8E6C9))
)
// 文字层
Text(
text = "Hello",
fontSize = 24.sp,
color = Color(0xFF2E7D32)
)
// 角标层
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.size(30.dp)
.background(Color.Red, shape = CircleShape)
)
}
}
你看,Box 的 contentAlignment 控制所有子组件的默认对齐,而每个子组件又可以通过 Modifier.align() 单独指定位置。这种「全局默认 + 局部覆盖」的设计,我个人觉得非常优雅。
二、Modifier 修饰符详解
Modifier 是 Compose 布局的灵魂。没有 Modifier,你连给组件加个背景色都做不到。它本质上是一个「装饰器模式」的实现——每个 Modifier 函数返回一个新的 Modifier,链式调用形成一条处理链。
1. 常用 Modifier 分类
| 类别 | 常用函数 | 说明 |
|---|---|---|
| 尺寸 | size(), width(), height(), fillMaxSize(), fillMaxWidth(), fillMaxHeight() |
控制组件的宽高 |
| 内边距 | padding(), paddingFrom() |
设置组件内部与边缘的距离 |
| 外边距 | Compose 没有 margin,用 Spacer 或 offset() 替代 |
控制组件之间的间距 |
| 背景与边框 | background(), border() |
设置颜色、形状、边框 |
| 对齐 | align(), alignBy() |
在父布局中对齐 |
| 权重 | weight() |
在 Row/Column 中按比例分配空间 |
| 点击 | clickable() |
添加点击事件 |
2. Modifier 的链式调用顺序
这个点特别重要——Modifier 的顺序会影响最终效果。为什么?因为每个 Modifier 函数会创建一个新的布局节点,顺序决定了它们应用的先后。
// 顺序不同,效果不同
Text(
text = "Hello",
modifier = Modifier
.padding(16.dp) // 先加内边距
.background(Color.Red) // 再涂背景
)
Text(
text = "Hello",
modifier = Modifier
.background(Color.Red) // 先涂背景
.padding(16.dp) // 再加内边距
)
第一个例子:先 padding 再 background,背景会覆盖 padding 区域,文字周围有红色背景。第二个例子:先 background 再 padding,背景只涂在文字区域,padding 区域是透明的。你看,同样的代码,顺序一变,视觉效果完全不同。
三、尺寸与约束
Compose 的尺寸系统,核心是「约束」这个概念。每个组件都会收到一个「约束范围」——最小宽度、最大宽度、最小高度、最大高度。组件在这个范围内决定自己的实际尺寸。
1. 尺寸修饰符
@Composable
fun SizeModifiers() {
Column {
// 固定尺寸
Box(modifier = Modifier.size(100.dp).background(Color.Blue))
// 按比例填充
Box(modifier = Modifier.fillMaxWidth(0.5f).height(50.dp).background(Color.Green))
// 最小/最大尺寸
Box(
modifier = Modifier
.widthIn(min = 50.dp, max = 200.dp)
.heightIn(min = 30.dp)
.background(Color.Yellow)
)
}
}
widthIn 和 heightIn 这两个函数,我建议你多留意。它们不会强制组件变成某个尺寸,而是告诉组件「你可以在 xx 到 xx 之间自由选择」。这在做自适应布局时特别有用。
2. 约束的传递机制
父组件会给子组件传递约束,子组件在约束范围内计算自己的尺寸,然后父组件根据子组件的尺寸决定自己的布局。这个过程是单向的——从父到子。
举个例子:
Row(
modifier = Modifier.fillMaxWidth()
) {
Text("短文本", modifier = Modifier.weight(1f))
Text("这是一段很长的文本,可能会换行", modifier = Modifier.weight(2f))
}
这里 Row 的宽度是屏幕宽度,它把约束传递给两个 Text。因为用了 weight,Row 会把可用空间按 1:2 分配给两个 Text。第一个 Text 拿到 1/3 的宽度,第二个拿到 2/3。
四、对齐方式
对齐方式分两种:一种是父布局层面的对齐(通过 Arrangement 和 Alignment 参数),另一种是子组件层面的对齐(通过 Modifier.align())。
1. 父布局对齐参数
| 布局 | 主轴对齐 | 交叉轴对齐 |
|---|---|---|
| Column | verticalArrangement |
horizontalAlignment |
| Row | horizontalArrangement |
verticalAlignment |
| Box | contentAlignment(同时控制两个轴) |
|
Arrangement 的常用值:Start、End、Center、SpaceBetween、SpaceAround、SpaceEvenly。Alignment 的常用值:Top、Bottom、CenterVertically、CenterHorizontally。
2. 子组件单独对齐
有时候你希望某个子组件「特立独行」,不跟其他组件走同一个对齐方式。这时候用 Modifier.align()。
Row(
modifier = Modifier.fillMaxWidth().height(100.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text("居中", modifier = Modifier.align(Alignment.CenterVertically))
Text("顶部", modifier = Modifier.align(Alignment.Top))
Text("底部", modifier = Modifier.align(Alignment.Bottom))
}
你看,Row 默认让所有子组件垂直居中,但第二个和第三个 Text 通过 align() 覆盖了默认行为。这种灵活性,传统 View 体系里得写不少代码才能实现。
五、知识体系总览
下面这张图,是我自己梳理的布局知识结构。你把它存下来,以后写布局时对照着看,思路会清晰很多。
六、实战小技巧
最后分享几个我在项目中积累的经验:
- 能用 Box 就别嵌套:很多新手喜欢 Column 套 Row 再套 Column,其实一个 Box 加几个 align() 就能搞定。嵌套越少,性能越好,代码也越清晰。
- Modifier 顺序记不住?记住「尺寸 → 背景 → 内边距 → 点击」这个口诀。虽然不绝对,但覆盖了 90% 的场景。
- 调试布局用 border:我经常给组件加一个
.border(1.dp, Color.Red),这样能直观看到每个组件的边界。调试完再删掉,比用 Layout Inspector 快多了。 - weight 和 fillMaxWidth 别混用:如果你在 Row 里给一个子组件同时用了 weight 和 fillMaxWidth,weight 会覆盖 fillMaxWidth。因为 weight 会重新分配约束。
好了,布局基础的上半部分就到这里。Column、Row、Box 这三个组件,配合 Modifier 的灵活运用,已经能解决日常开发中 80% 的布局需求。剩下的 20%,我们会在下一节「布局基础(下)」里继续聊——包括 Scaffold、LazyColumn、ConstraintLayout 这些更高级的布局工具。