[Android Compose] modifier的顺序是怎么影响最终效果的,为什么padding的不同顺序可以表示内/外边距

网上的很多文章在分析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~280width的(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。最后控件根据约束和自己的需求,确定自己的大小。

具体图示如下:


image.png

控件大小确定后,会向上传递,例如传递到第二个paddingpadding在收到控件的大小后,会根据

  1. 上层给它的约束
  2. 控件的大小
  3. 自己的想要的大小

来确定自己的大小。具体来看,在收到控件的大小后,padding会在控件大小的基础上加上水平和竖直的间距作为自己的大小,如果超过最大最小值约束,则将大小限制在约束内。

//placeable是下级节点
val width = constraints.constrainWidth(placeable.width + horizontal) 
val height = constraints.constrainHeight(placeable.height + vertical)

所以,图中Text的大小事100x100,第二个padding的大小是120x120,background的大小是120x120,第一个padding的大小是140x140。图示如下:

image.png

所以最早效果是: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)的位置。

所以最终的效果是:


image.png

所以backgroup前面的padding是外边距,后面的padding是内边距。

哪里感性的认知是什么呢,执行到第一个padding,子组件往右/下移动10dp,然后画背景色,然后边缘在往右和往下移10dp,在画子组件。

附几篇个人感觉写的比较好的文章:

  1. Constraints and modifier order
  2. Compose 是如何将数据转换成 UI 的?
  3. 解刨「布局修饰符」 -- Modifier.layout()、LayoutModifier
  4. Compose把数据转化为UI的三个阶段
  5. LayoutModifierNode 和 Modifier.layout()
  6. Modifier 是怎么工作的?
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容