网上的很多文章在分析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,在画子组件。
附几篇个人感觉写的比较好的文章: