正文
fun interface MeasurePolicy {
fun MeasureScope.measure(
measurables: List<Measurable>,
constraints: Constraints
): MeasureResult
}
MeasurePolicy 通过 measure 方法完成测量。这里有两个重要参数:
-
measurables
: 等待测量的对象,其实就是当前节点的子节点
-
constraints
: 测量约束。节点需要基于当前的 Constaints 进行测量,它规定了节点尺寸的上限和下限,如下:
class Constraints {
val minWidth: Int
val maxWidth: Int
val minHeight: Int
val maxHeight: Int
...
}
父节点通过
Constraints
约束子节点的测量。Constraints 非常重要,我们常说 Compose 不怕布局嵌套正是得益于它。反观 Android 原生视图,由于测量阶段的约束不明确,子 View 需要再次请求父 View 给出清楚的
View.MeasureSpec
,导致出现多次绘制。
举几个例子理解一下 Constraints 如何设置:
对于页面的根节点, Activity 的 Window 的长宽就是其 Constraints 的最大长宽。如果是一个垂直可滚动容器的节点,那么它的 Constraints 的 height 应该是 Infinity,因为它可以跨多个屏幕存在。
此外, Modifier 的装饰能力本质也是通过修改 Constraints 完成的。例如
fillMaxWidth
要求被修饰的节点填充整个父容器,所以 Modifier 会在布局阶段将
minHeight/minWidth
对齐 max 组值。关于 Modifier 参与布局的流程,稍后介绍。
我们实现一个类似 Column 的布局效果,在
measurePolicy#measure
中实现三步走逻辑。
measurePolicy = {
val placeables = measurables.map { measurable ->
measurable.measure(constraints)
}
val height = placeables.sumOf { it.height }
val width = placeables.maxOf { it.width }
layout(width, height) {
var yPosition = 0
placeables.forEach { placeable ->
placeable.placeRelative(x = 0, y = yPosition)
yPosition += placeable.height
}
}
}
-
每个
measuable
提供了参与测量的
measure
方法,此处会传入 Constraints,返回的
placeable
中已经存储了测量后的
widht
和
height
,等待
place
-
基于各个
placeable
的
w
和
h
计算当前节点的 Size,并通过
layout
方法设置。
layout
方法内会真正的创建
LayoutNode
-
layout
方法的末参是一个 lambda,这里是第三步摆放子节点的逻辑,通过设置 y 轴的偏移量实现纵向布局,非常简单
特别值得一提的是,通过
meause
一个方法就完成三步走,布局逻辑相对传统的 View 系统更加高效,回想传统自定义 View 你需要分别实现
onMeasure
,
onLayout
,
onDraw
等,逻辑分散,可读性差。
但是这种集中式的写法有一个弊端,需要人为保证代码顺序。试想如果把
layout
写在
measure