4.1 声明式布局思维转变
传统的 XML 布局是嵌套约束型——你用 LinearLayout 或 ConstraintLayout 来约束子 View 的位置,布局参数和 View 的层级在 XML 中静态定义。
Compose 的布局是函数组合型——你用 Kotlin 函数嵌套来描述 UI 结构,布局参数通过函数参数和 Modifier 动态控制。
// XML 方式(命令式层次结构)
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView android:text="Hello"/>
<Button android:text="Click"/>
</LinearLayout>
// Compose 方式(函数式组合)
Column(
verticalArrangement = Arrangement.Top,
modifier = Modifier.fillMaxWidth()
) {
Text("Hello")
Button(onClick = {}) { Text("Click") }
}
区别总结:
| 维度 | XML | Compose |
|---|---|---|
| 布局容器 | LinearLayout、RelativeLayout 等 | Row、Column、Box |
| 参数传递 | XML attribute | 函数参数 + Modifier |
| 动态控制 | 运行时通过代码修改 | 声明时直接使用 Kotlin 表达式 |
| 条件显示 | visibility = View.GONE | 直接 if 表达式控制是否组合 |
4.2 三大基本布局组件
Row —— 水平排列
@Composable
fun Row(
modifier: Modifier = Modifier,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top,
content: @Composable RowScope.() -> Unit
)
Row(
modifier = Modifier.fillMaxWidth().padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Icon(...)
Text("Title")
Switch(...)
}
Arrangement 详解:
| Arrangement | 效果 |
|---|---|
Start / End | 从主轴起点/终点排列 |
Center | 居中 |
SpaceBetween | 首尾靠边,剩余均匀分配中间空隙 |
SpaceEvenly | 均匀分配空间,包括首尾 |
SpaceAround | 每个元素左右均匀分配,首尾空间为一半 |
RowScope 中的特殊 Modifier:
Row {
Text("1", Modifier.weight(1f)) // 占据 1/3 空间
Text("2", Modifier.weight(2f)) // 占据 2/3 空间
Text("3") // 自适应
}
RowScope.weight只在Row的 content lambda 中可用,这是 Scope Receiver 模式的典型应用——限制特定 API 的作用域。
Column —— 垂直排列
@Composable
fun Column(
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: @Composable ColumnScope.() -> Unit
)
Column(
modifier = Modifier.fillMaxSize().padding(24.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Title", style = MaterialTheme.typography.headlineLarge)
Spacer(Modifier.height(16.dp))
Text("Subtitle")
Spacer(Modifier.height(32.dp))
Button(onClick = {}) { Text("Get Started") }
}
Box —— 层叠布局(类似 FrameLayout)
@Composable
fun Box(
modifier: Modifier = Modifier,
contentAlignment: Alignment = Alignment.TopStart,
content: @Composable BoxScope.() -> Unit
)
Box(
modifier = Modifier.size(200.dp),
contentAlignment = Alignment.BottomEnd
) {
// 背景层
AsyncImage(
model = imageUrl,
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
// 覆盖层
Surface(
color = Color.Black.copy(alpha = 0.5f),
modifier = Modifier.padding(4.dp)
) {
Text("Copyright", color = Color.White)
}
}
Box 的典型场景:
- 占位/占位图:在图片上叠加文字或按钮
- 徽标 Badge:在头像上叠红色的未读数字
- 全屏加载:半透明遮罩 + 转圈
4.3 Scope Receiver 模式
这是一种在 Compose 中广泛使用的设计模式。通过限制 content 参数的类型,让子 Composable 只能访问特定作用域内的 API。
// RowScope、ColumnScope、BoxScope
@LayoutScopeMarker
interface RowScope {
fun Modifier.weight(weight: Float): Modifier
}
@LayoutScopeMarker
interface ColumnScope {
fun Modifier.weight(weight: Float): Modifier
fun Modifier.align(alignment: Alignment.Horizontal): Modifier
}
作用:
Column {
Text("Top", Modifier.align(Alignment.CenterHorizontally)) // ✅ ColumnScope 允许
Row {
Text("RowItem", Modifier.align(Alignment.CenterHorizontally)) // ❌ RowScope 没有 align
}
}
这种设计在编译期就避免了无效的 API 调用,比传统 View 系统在运行时忽略无效 layout_gravity 更安全。
4.4 Modifier 布局顺序的重要性
// 不同的 Modifier 顺序产生截然不同的效果
Box {
// 效果:先 fillMaxSize,再 padding 16dp
Text("Hello", Modifier.fillMaxSize().padding(16.dp))
// 效果:先 padding 16dp,再 fillMaxSize
Text("World", Modifier.padding(16.dp).fillMaxSize())
}
核心规律:
Modifier.fillMaxSize()是测量约束 →.padding()是缩小约束区域- 先
fillMaxSize()再padding():先撑满父容器,再在内边距区域内绘制 - 先
padding()再fillMaxSize():先加内边距,再撑满父容器,内边距被"撑"掉了
我们将在第五篇文章中深度解析 Modifier 系统。
4.5 ConstraintLayout 在 Compose 中
当布局层级较深或多约束依赖时,使用 Compose 的 ConstraintLayout:
implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0")
import androidx.constraintlayout.compose.*
@Composable
fun ConstraintExample() {
ConstraintLayout(
modifier = Modifier.fillMaxSize()
) {
// 1. 创建引用
val (avatar, name, badge) = createRefs()
// 2. 约束条件
AsyncImage(
model = url,
contentDescription = "Avatar",
modifier = Modifier.constrainAs(avatar) {
top.linkTo(parent.top, margin = 16.dp)
start.linkTo(parent.start, margin = 16.dp)
width = Dimension.value(48.dp)
height = Dimension.value(48.dp)
}
)
Text(
text = "John Doe",
modifier = Modifier.constrainAs(name) {
top.linkTo(avatar.top)
start.linkTo(avatar.end, margin = 12.dp)
end.linkTo(parent.end, margin = 16.dp)
width = Dimension.fillToConstraints
}
)
}
}
何时使用 ConstraintLayout:
- 布局扁平化(避免多层嵌套 Row + Column)
- 复杂的相对定位(A 在 B 的右下角,C 在 A 的下方等)
- 需要在 Compose 中移植已有的 XML ConstraintLayout 布局
何时避免:
- 简单的线性布局用 Row / Column 更清晰
- 层叠布局用 Box 更简单
4.6 IntrinsicSize 模式
在某些场景下,你需要基于子组件的"内在尺寸"来确定父容器尺寸:
// 让 Row 的高度由最高的子元素决定
Row(
modifier = Modifier.height(IntrinsicSize.Min) // 或 IntrinsicSize.Max
) {
Text("Tall\nText", Modifier.height(80.dp))
Text("Normal")
Button(onClick = {}) { Text("Go") }
}
IntrinsicSize.Min 取所有子组件内在最小尺寸的最大值。
IntrinsicSize.Max 取所有子组件内在最大尺寸的最大值。
这在"所有子组件等高"或"父容器适应子内容"时非常有用,避免硬编码高度。
4.7 本章小结
| 内容 | 要点 |
|---|---|
| Row | 水平排列,Arrangement 控制间距,weight 按比例分配空间 |
| Column | 垂直排列,同理 Arrangement + weight |
| Box | 层叠布局,适合覆盖层、Badge、加载遮罩 |
| Scope Receiver | RowScope/ColumnScope/BoxScope 限定特定 API 的调用范围 |
| 布局顺序 | fillMaxSize() → padding() 与 padding() → fillMaxSize() 效果不同 |
| 布局选择 | 简单线性用 Row/Column,层叠用 Box,复杂约束用 ConstraintLayout |
下一篇:Modifier 深度解析——链式调用的艺术。