Compose UI
Compose 底层 · androidx.compose.ui
测量与布局1.13.0-alpha01
Constraints、Measurable、Placeable 单遍测量模型,以及自定义 Layout 的正确姿势。
最后更新 2026-08-01
数据流
父节点向子节点传 Constraints(宽高范围),子节点 measure() 后返回 Placeable,父节点决定自身尺寸并放置子节点:
@Composable
fun VerticalStack(content: @Composable () -> Unit) {
Layout(content = content) { measurables, constraints ->
val placeables = measurables.map { it.measure(constraints) }
val width = placeables.maxOfOrNull { it.width } ?: 0
val height = placeables.sumOf { it.height }
layout(
width.coerceIn(constraints.minWidth, constraints.maxWidth),
height.coerceIn(constraints.minHeight, constraints.maxHeight),
) {
var y = 0
placeables.forEach {
it.placeRelative(0, y)
y += it.height
}
}
}
}Constraints 详解
Constraints(
minWidth = 0, // 最小允许宽度(px)
maxWidth = 100.dp.roundToPx(),
minHeight = 0,
maxHeight = 100.dp.roundToPx(),
)- 有界/无界:
max = Constraints.Infinity表示”想多大就多大”(scrollable 容器里常见)。 - 固定约束:min == max 时子项必须取该尺寸(
requiredSize的语义)。 - 常用构造:
constraints.copy(minWidth = ...)constraints.constrain(width)把值收进范围
两条硬规则
- 返回尺寸必须落在父约束范围内——超出会崩溃或行为未定义。
- 普通测量阶段一个 child 不应重复 measure——测量有成本;多阶段决策用 intrinsic 测量或 Subcompose。
单遍测量的含义
Compose 是单遍测量模型(与 View 的双遍不同):
| 模型 | 行为 |
|---|---|
| View(双遍) | measure 可能被调用多次,父可”试”尺寸 |
| Compose(单遍) | 每个 child 在正常测量阶段只 measure 一次,保证性能 |
需要”先知道 child 理想尺寸再决定”时:
- 用 intrinsic 测量(
measureIntrinsic系列)做粗算。 - 或用
SubcomposeLayout(动态子组合)。
约束子项测量
生产布局通常不给所有 child 同样的 constraints:
Layout(content) { measurables, constraints ->
// 第一个 child 占满高度,第二个 child 剩余宽度
val fixed = measurables[0].measure(constraints.copy(maxWidth = 200.dp.roundToPx()))
val remaining = measurables[1].measure(
constraints.copy(
minWidth = 0,
maxWidth = (constraints.maxWidth - fixed.width).coerceAtLeast(0),
)
)
...
}先用 Foundation
| 布局 | 用途 |
|---|---|
Row / Column | 线性排列 + 权重 |
Box | 叠放 + 对齐 |
LazyColumn/Row | 大列表 |
FlowRow/Column | 自动换行 |
只有这些表达不了你的规则时才直接用 Layout——自定义测量会引入 intrinsic、RTL、alignment line 和性能负担。
复制即用
@Composable
fun ProportionalBox(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
Layout(modifier = modifier, content = content) { measurables, constraints ->
val placeables = measurables.map { it.measure(constraints) }
val width = constraints.maxWidth
val height = constraints.maxHeight
layout(width, height) {
placeables.forEach { it.place(0, 0) }
}
}
}常见陷阱
- 重复 measure 子项:单遍测量约束下每个 child 只 measure 一次,多遍会报错。
- 忘处理 constraints 无限值:
constraints.maxWidth == Constraints.Infinity时直接 layout 会崩。 - 自带 Layout 重造轮子:Row/Column/Box/Flow 能表达就别用 Layout。
- 忽略 intrinsic/alignment/RTL:自定义 Layout 要自己承担这些语义。
要点
- 父传 Constraints → 子 measure → Placeable → 父 layout + place。
- 返回尺寸必须在约束内;正常测量阶段 child 只 measure 一次。
- 需要预知尺寸用 intrinsic 或 Subcompose;不要重复 measure。
- 先考虑 Row/Column/Box,自定义 Layout 是最后手段。