D1布局三件套
Column / Row / Box 是拼界面的底座。绝大多数常见页,先用它们搭骨架,再用Modifier调尺寸边距。
🔗 引用:布局基础
@Composable 函数体内。尺寸、边距等 Modifier 链式写法下一章细讲,本章只带最常用的。Column ★
概念
Column 把子项从上到下排成一列。像竖着的货架:标题、表单项、按钮依次往下叠。
和 XML 对照
| Compose | XML 近似 |
|---|---|
Column | LinearLayout + android:orientation="vertical" |
verticalArrangement | gravity / 间距靠 margin 硬撑 |
horizontalAlignment | 子 View 的 layout_gravity / gravity |
怎么用
★ 常用参数:
modifier— 整列的尺寸、边距、背景verticalArrangement— 子项在竖直方向怎么分布(顶对齐、均分、底部、间距)horizontalAlignment— 子项在水平方向相对列怎么齐(左 / 居中 / 右)
@Composable
fun ColumnBasics() {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.Start
) {
Text("标题")
Text("说明文字")
Button(onClick = { }) {
Text("主按钮")
}
}
}
易错点
- 内容超出屏幕却不滚动——长页要在 Column 上挂滚动(或改用 Lazy),别指望默认能滑。
- 把「子项之间的间距」全靠每个子项自己
padding——优先Arrangement.spacedBy,更整齐。
▶ 设置页:标题 + 一列表单项
设置页典型骨架:顶上标题,下面一列「标签 + 说明」行。XML 常用竖向 LinearLayout;Compose 用 Column + 行内再用 Row(见下一节)。
@Composable
fun SettingsSkeleton() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text("设置", style = MaterialTheme.typography.titleLarge)
SettingsItem(title = "账号", subtitle = "登录与安全")
SettingsItem(title = "通知", subtitle = "推送与提醒")
SettingsItem(title = "关于", subtitle = "版本与协议")
}
}
@Composable
fun SettingsItem(title: String, subtitle: String) {
Column(Modifier = Modifier.fillMaxWidth()) {
Text(title)
Text(subtitle, color = MaterialTheme.colorScheme.onSurfaceVariant)
}
}
要点:外层 Column 管「整页竖排」;每一行可以是更小的 Column(两行字)或 Row(左文右箭头)。
Row ★
概念
Row 把子项从左到右排成一行。列表行、工具栏、按钮组,几乎都先想到它。
和 XML 对照
| Compose | XML 近似 |
|---|---|
Row | LinearLayout + orientation="horizontal" |
horizontalArrangement | 水平 gravity / 间距 |
verticalAlignment | 子项垂直方向 gravity |
Modifier.weight(1f) | layout_weight |
怎么用
★ 常用参数:
horizontalArrangement— 水平分布(靠左、两端对齐、均分、间距)verticalAlignment— 行内垂直对齐(顶 / 居中 / 底)Modifier.weight— 仅在 Row/Column 直接子项上按比例分剩余空间(≈layout_weight)
@Composable
fun RowBasics() {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text("左侧")
Text("右侧")
}
}
@Composable
fun RowWithWeight() {
Row(modifier = Modifier.fillMaxWidth()) {
Text(
"占满剩余",
modifier = Modifier.weight(1f)
)
Text("固定尾")
}
}
易错点
weight写在非 Row/Column 直接子项上——无效;必须是该作用域里的直接孩子。- 想「中间文字撑开、两边图标固定」却忘了给中间项
weight(1f)。 - 行内多行文字垂直不齐——给 Row 设
verticalAlignment = Alignment.CenterVertically。
▶ 列表行:头像 + 两行文字 + 尾箭头
经典列表 cell:左圆形头像,中间标题+副标题竖排,右侧箭头。XML 常是横向 LinearLayout 嵌套;Compose 是 Row 里塞 Column。
@Composable
fun ListRowDemo(
title: String,
subtitle: String,
onClick: () -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
// 头像占位:实战换成 Image / AsyncImage
Box(
modifier = Modifier
.size(40.dp)
.background(MaterialTheme.colorScheme.primaryContainer)
)
Column(
modifier = Modifier
.weight(1f)
.padding(horizontal = 12.dp)
) {
Text(title)
Text(
subtitle,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Text("›")
}
}
要点:中间 Column 用 weight(1f) 吃掉剩余宽度,箭头才会被顶到最右。
Box ★
概念
Box 让子项叠在同一区域(后写的默认画在上面)。适合底图+角标、内容+加载圈、全屏蒙层。
和 XML 对照
| Compose | XML 近似 |
|---|---|
Box | FrameLayout |
子项 Modifier.align(...) | layout_gravity |
contentAlignment | 默认把未单独 align 的子项往哪靠 |
怎么用
contentAlignment— 没单独指定 align 的子项,默认对齐点(常用来整页居中一个加载圈)- 子项
Modifier.align(Alignment.TopEnd)等 — 在 Box 里精确定位(★)
@Composable
fun BoxBasics() {
Box(
modifier = Modifier.size(120.dp),
contentAlignment = Alignment.Center
) {
Text("底层")
Text(
"角标",
modifier = Modifier.align(Alignment.TopEnd)
)
}
}
易错点
- 想居中却用一堆 padding「顶」——在 Box 里用
align/contentAlignment。 - 把 Box 当成万能容器乱套——能横排用 Row、能竖排用 Column,Box 留给「需要叠」的场景。
- 叠放顺序:写在后面的子项默认更靠上;必要时再用
zIndex(进阶)。
▶ 图片上叠加载圈 / 角标
封面图加载中要转圈,加载完显示角标「NEW」。XML 用 FrameLayout;Compose 用 Box 叠三层。
@Composable
fun CoverWithOverlay(
loading: Boolean,
showBadge: Boolean
) {
Box(
modifier = Modifier.size(160.dp),
contentAlignment = Alignment.Center
) {
// 底图占位:实战换成 Image / AsyncImage
Box(
modifier = Modifier
.matchParentSize()
.background(MaterialTheme.colorScheme.surfaceVariant)
)
if (loading) {
CircularProgressIndicator()
}
if (showBadge) {
Text(
"NEW",
modifier = Modifier
.align(Alignment.TopEnd)
.padding(8.dp)
)
}
}
}
要点:matchParentSize() 让底图铺满 Box;加载圈靠 contentAlignment 居中;角标单独 align(TopEnd)。
何时用谁
概念
先问「子项之间的关系是竖、横,还是叠」,再选容器。复杂界面 = 三者嵌套,而不是找一个「超级布局」。
和 XML 对照
| 需求 | XML 常选 | Compose 常选 |
|---|---|---|
| 从上到下 | 竖向 LinearLayout | Column |
| 从左到右 | 横向 LinearLayout | Row |
| 叠在一起 | FrameLayout | Box |
| 复杂约束网 | ConstraintLayout | 先嵌套三件套;真不够再 Constraint(扩展) |
怎么用(选型口诀)
- 设置页 / 表单 → 先
Column - 列表一行 / 顶栏按钮 → 先
Row - 角标、加载遮罩、悬浮在内容上 → 先
Box - 一行里既有横排又有两行字 →
Row { 头像; Column { 标题; 副标题 } }
易错点
- 一上来 ConstraintLayout「保险」——日常页用三件套嵌套更清晰,Modifier 下一章补齐。
- 嵌套过深(五六层)——把重复块抽成小
@Composable,而不是继续往里塞。
▶ 一个简单卡片头:Box + Row + Column
卡片头:圆角底(Box)上放一行(Row):左边头像+两行字(Column),右边操作按钮。
@Composable
fun CardHeaderDemo() {
Box(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceVariant)
.padding(16.dp)
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.size(48.dp)
.background(MaterialTheme.colorScheme.primary)
)
Column(
modifier = Modifier
.weight(1f)
.padding(horizontal = 12.dp)
) {
Text("昵称")
Text("一句话简介")
}
TextButton(onClick = { }) {
Text("关注")
}
}
}
}
要点:Box 当「底板」;Row 管水平分区;Column 管文字竖排。尺寸圆角等细节留给下一章 Modifier。