measurePolicy 是 Compose 布局系统的核心,它定义了布局的"测量与摆放"规则。你可以把它理解为一个布局算法的说明书,它回答了三个关键问题:
- 如何测量每个子项?
- 布局自身应该多大?
- 如何摆放每个子项?
它通常与 Layout 可组合项或 Modifier.layout 修饰符配合使用。
🎯 核心概念与流程
measurePolicy 的工作流程围绕 MeasureScope.measure 方法展开,其输入是约束(Constraints)和子项列表(List<Measurable>,代表所有子组件),输出是布局结果(MeasureResult)。

📝 基础代码模板
理解上图后,我们通过一个最简单的竖向排列并居中布局的代码来理解其实现:
kotlin
@Composable
fun VerticalCenteredLayout(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
// 使用 Layout 可组合项,传入内容和自定义的 measurePolicy
Layout(
modifier = modifier,
content = content
) { measurables, constraints -> // 这里就是 measurePolicy 的 lambda
// 1. 测量所有子项(不受约束,用最宽松的)
val placeables = measurables.map { measurable ->
measurable.measure(constraints.copy(minWidth = 0, minHeight = 0))
}
// 2. 计算自身尺寸:宽度取子项最大宽度,高度为子项高度之和
val width = placeables.maxOfOrNull { it.width } ?: 0
val height = placeables.sumOf { it.height }
// 3. 布局:摆放每个子项,并返回最终布局结果
layout(width, height) {
var y = 0
placeables.forEach { placeable ->
// 水平居中摆放
val x = (width - placeable.width) / 2
placeable.placeRelative(x = x, y = y)
y += placeable.height // 垂直位置累加
}
}
}
}
// 使用
VerticalCenteredLayout(Modifier.fillMaxSize()) {
Text("First")
Text("Second with longer text")
Button(onClick = {}) { Text("Button") }
}
🛠️ 两种主要使用方式
1. 通过 Layout 可组合项创建自定义布局
如上例所示,这是创建全新布局组件的主要方式。Layout 的最后一个参数 measurePolicy 正是你的布局算法。
2. 通过 Modifier.layout 调整单个组件的测量布局
这种方式只影响被修饰的组件本身及其直接子项 (如果它有的话,如 Box),不会创建全新的布局组件。常用于微调。
kotlin
@Composable
fun PaddingFromBaseline(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
content = content,
modifier = modifier
) { measurables, constraints ->
val placeable = measurables.first().measure(constraints)
// 假设从基线向上有 50.dp 的固定内边距要求
val firstBaseline = placeable[FirstBaseline]
val padding = if (firstBaseline != AlignmentLine.Unspecified) {
(50.dp.roundToPx() - firstBaseline).coerceAtLeast(0)
} else {
0
}
layout(placeable.width, placeable.height + padding) {
placeable.placeRelative(0, padding)
}
}
}
⚙️ 关键技巧与深度解析
1. 理解并使用 Constraints
Constraints 是父布局传递给子项的限制条件,它定义了子项尺寸的允许范围:
minWidth/maxWidthminHeight/maxHeight你可以根据布局逻辑,为每个子项创建新的约束。
示例:让子项占自身宽度的一半
kotlin
measurable.measure(
constraints.copy(
maxWidth = constraints.maxWidth / 2,
minWidth = constraints.minWidth / 2
)
)
2. 处理多子项与测量顺序
对于多个子项,你可能需要多次测量 或使用 IntrinsicMeasurable 进行固有特性测量。
固有特性测量示例(实现一个所有子项宽度均分的布局):
kotlin
Layout(content = content) { measurables, constraints ->
val itemWidth = constraints.maxWidth / measurables.size
val itemConstraints = constraints.copy(minWidth = itemWidth, maxWidth = itemWidth)
val placeables = measurables.map { it.measure(itemConstraints) }
val height = placeables.maxOfOrNull { it.height } ?: constraints.minHeight
layout(constraints.maxWidth, height) {
var x = 0
placeables.forEach { placeable ->
placeable.placeRelative(x = x, y = 0)
x += itemWidth
}
}
}
3. 使用 Placeable 的扩展能力
Placeable 不仅有宽高,还提供:
- 对齐线 :
placeable[FirstBaseline]获取文字的基线。 - 多层放置 :
placeable.placeWithLayer支持硬件加速层。 - 自定义放置 :在
layout块中,你可以完全自由地决定每个子项的位置。
💎 最佳实践与常见误区
| 最佳实践 | 说明 |
|---|---|
| 尽可能只测量一次 | 多次测量(measurable.measure)是昂贵的,可能导致性能问题。 |
| 正确传递约束 | 不要随意忽略父级约束(如 minWidth),这可能导致布局在特定情况下崩溃。 |
使用 layoutId 标识子项 |
在复杂布局中,通过 Modifier.layoutId 标记子项,便于在 measurePolicy 中识别和处理。 |
| 为自定义布局提供对齐线 | 通过 AlignmentLine 让外部布局能对齐你的自定义布局内部的特定位置(如文本基线)。 |
常见误区:
kotlin
// ❌ 错误:忽略了父级的最小高度约束
val placeable = measurable.measure(Constraints.fixedWidth(100))
// ✅ 正确:在固定宽度的同时,尊重高度约束
val placeable = measurable.measure(
Constraints(
minWidth = 100,
maxWidth = 100,
minHeight = constraints.minHeight, // 传递父级约束
maxHeight = constraints.maxHeight
)
)
🆚 与传统 View 系统的对比
| 传统 View 系统 | Compose (measurePolicy) |
|---|---|
继承 ViewGroup,重写 onMeasure 和 onLayout |
实现 MeasurePolicy 接口或使用 lambda |
使用 MeasureSpec 表示约束 |
使用 Constraints 对象,概念更统一 |
通过 child.measure 和 child.layout 操作 |
通过 measurable.measure 和 placeable.placeRelative |
getChildAt 按添加顺序访问 |
通过 measurables 列表访问,顺序由组合决定 |
总结 :measurePolicy 是 Compose 布局系统的"算法心脏"。掌握它,你就能创造出完全符合设计需求的任意布局。从简单的堆叠布局到复杂的瀑布流、环形菜单,都基于这一套统一的测量-摆放模型。