测量与布局 · AndroidX 源码指南
AAndroidX 源码指南
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) 把值收进范围

两条硬规则

  1. 返回尺寸必须落在父约束范围内——超出会崩溃或行为未定义。
  2. 普通测量阶段一个 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 是最后手段。

相关页面