网上的很多文章在分析Modifier时,都在Modifier展开为一维列表结构后戛然而止,看完之后就有个疑问,展开之后呢??
先总结下其他人的结论,Modifier链式调用经过处理后,成为了一个一维列表,其中顺序与链式调用一致。例如:
Modifier
.padding(10.dp)
.background(Color.Gray)
.padding(10.dp)
会被处理为:[PaddingElement,BackgroundElement,PaddingElement],注意:每个都是Element
那么接下来,处理为一维列表之后呢?
以上面的例子为例,padding是通过修改测量时的约束生效的,所以需要先弄明白Compose的测量过程。推荐这篇文章:元素的测量和布局过程。约束是自上向下传递的,当某个控件使用Modifier时,会将约束传递给具体的Element,比如传递给PaddingElement。此时的Element就可以修改约束,并将修改后的约束向下传递。
ps:更正确的说,约束不是传递给Element,而是传递给Element里面的Node,并且也不是所有Node都可以获得并修改约束,只有继承自LayoutModifierNode的才可以。
以上面具体的例子为例,约束是怎么传递的呢?
假设我们有一个200x300的画布。
所以第一个padding获取的约束是width=0~200,height=0~300。然后padding修改了这个约束,具体怎么改的,看下PaddingNode的实现:
private class PaddingNode(
var start: Dp = 0.dp,
var top: Dp = 0.dp,
var end: Dp = 0.dp,
var bottom: Dp = 0.dp,
var rtlAware: Boolean
) : LayoutModifierNode, Modifier.Node() {
override fun MeasureScope.measure(
measurable: Measurable, //后面的节点
constraints: Constraints //约束
): MeasureResult {
val horizontal = start.roundToPx() + end.roundToPx()
val vertical = top.roundToPx() + bottom.roundToPx()
val placeable = measurable.measure(constraints.offset(-horizontal, -vertical)) //这里修改了约束,并向下传递
val width = constraints.constrainWidth(placeable.width + horizontal)
val height = constraints.constrainHeight(placeable.height + vertical)
return layout(width, height) {
if (rtlAware) {
placeable.placeRelative(start.roundToPx(), top.roundToPx())
} else {
placeable.place(start.roundToPx(), top.roundToPx())
}
}
}
}
PaddingNode中可以看到,其是将约束整个向左平移:水平间距的值。并保证最小值>=0 (在constraints.offset(-horizontal, -vertical)方法内部)。
所以修改后的约束是:width=0~200 - (10 + 10) = 0~180,height=0~300 - (10+10) = 0~280。width的(10+10)表示左边 + 右边的间距 ,height同理。(我们前面padding(10.dp)的结果)
所以传递给Background的约束变成了width=0~180,height=0~280。然后Background是怎么修改约束的呢?Background不能修改约束,为什么?因为它没有继承LayoutModifierNode。
然后约束继续向下传递,到了第二个padding(10.dp),按照上面的逻辑,其修改约束后再传递给具体的控件.所以,具体控件获得的约束是w=0~160,h=0~260。最后控件根据约束和自己的需求,确定自己的大小。
具体图示如下:

控件大小确定后,会向上传递,例如传递到第二个padding,padding在收到控件的大小后,会根据
- 上层给它的约束
- 控件的大小
- 自己的想要的大小
来确定自己的大小。具体来看,在收到控件的大小后,padding会在控件大小的基础上加上水平和竖直的间距作为自己的大小,如果超过最大最小值约束,则将大小限制在约束内。
//placeable是下级节点
val width = constraints.constrainWidth(placeable.width + horizontal)
val height = constraints.constrainHeight(placeable.height + vertical)
所以,图中Text的大小事100x100,第二个padding的大小是120x120,background的大小是120x120,第一个padding的大小是140x140。图示如下:

所以最早效果是:Text占了100x100的大小,第二个padding占了120x120的大小,background占了120x120的大小,第一个padding占了140x140的大小。到这里,各个修饰符的大小确定了,那么怎么摆放,在看padding:
return layout(width, height) {
if (rtlAware) {
placeable.placeRelative(start.roundToPx(), top.roundToPx()) //这里摆放
} else {
placeable.place(start.roundToPx(), top.roundToPx()) //这里摆放
}
}
padding摆放是具体上一个修饰符(父修饰符)左上角的(start,top)的位置。
所以最终的效果是:

所以backgroup前面的padding是外边距,后面的padding是内边距。
哪里感性的认知是什么呢,执行到第一个padding,子组件往右/下移动10dp,然后画背景色,然后边缘在往右和往下移10dp,在画子组件。
附几篇个人感觉写的比较好的文章: