LayoutModifier

作用是修改 Composable 的尺寸和位置偏移

LayoutModifier 只是一个接口,在实际开发中我们会使用 Modifier.layout() 自定义测量和位置偏移处理,先大致了解一下 layout 源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 实际的运用,调用 Modifier.layout 来修改测试和位置偏移处理
fun Modifier.layout(
    measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) = this then LayoutElement(measure)

private data class LayoutElement(
    val measure: MeasureScope.(Measurable, Constraints) -> MeasureResult //  lambda 函数
) : ModifierNodeElement<LayoutModifierImpl>() {
    override fun create() = LayoutModifierImpl(measure)

    override fun update(node: LayoutModifierImpl) {
        node.measureBlock = measure
    }

    override fun InspectorInfo.inspectableProperties() {
        name = "layout"
        properties["measure"] = measure
    }
}

// 测量结果
interface MeasureResult {
    val width: Int 
    val height: Int
    val alignmentLines: Map<AlignmentLine, Int> // 一般用于 baseLine 的设置
    fun placeChildren() // 子组件的测量
}

在 Compose 中,如果我们要拦截控件的测量和摆放一般是使用的 Modifier.layout() 这个函数, 可以看到这个 LayoutModifier 最终的实现是 LayoutModifierImpl , 最终在这个 layout 的 lambda 表达式中,需要返回 MeasureResult 对象,而在 Compose 有一个 layout() 函数是 Compose 给我们封装好了的一个 MeasureResult 对象返回,其中 MeasureResult 维护了组件宽高,文字的基准线等等

测量和摆放

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Preview
@Composable
fun layoutModifier() {
    /** modifier.layout() 可以对当前的控件进行测量,和设置偏移,设置宽高的上限, 但是它仅仅是 针对于当前本控件的,不像传统 View 提供可以对 子view 进行操作 **/

    Box(modifier = Modifier
        .background(Color.Green)
        .layout { measurable, constraints ->
            // 测量本控件 (// 可以理解为 传统view 中测量自己大小的步骤 获得自己宽高的尺寸)
            val placeable = measurable.measure(constraints)

            // 设置当前组件尺寸
            layout(placeable.width, placeable.height) {
                // 摆放
                placeable.placeRelative(0, 0) 
            }
        }) {
        Text(text = "Hello World")
    }
}

上面代码中 layout 第一个参数 measurable 代表是一个可测量对象指被修饰的组件,constraints 代表的是外层组件对被修饰组件的尺寸限制

通过 measurable.measure(constraints) 开始进行测量当前组件,返回的 placeable 中,会包含测量好的宽高等信息,然后通过 layout 函数,将测量好的宽高进行结果返回,我们前面有说到 layout 需要返回一个 MeasureResult 对象, 这个 layout 函数内部其实是一个 MeasureResult 对象,然后再通过 placeable.placeRelative(0, 0) 设置偏移,这里我们设置的是 0,那么就是原封不动的,组件大小和偏移都没有经过任何的修改,这个代码运行后是这样的

设置边距

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Composable
fun layoutModifier2() {
    BoxWithConstraints(modifier = Modifier
        .background(Color.Green)
        .layout { measurable, constraints ->  //  测量本控件
            // 增加 10 dp
            val padding = 10.dp
            val placeable = measurable.measure(
                // 设置限制(约束)范围
                constraints.copy(
                    /** 对上界和下界扩大 , 乘以 2,是因为,左右上下都有padding**/

                    /**
                     * 增加 padding, 这里的限制为什么要设减去 padding 呢 ?
                     *
                     * 这是因为当前控件增加了 padding, 那么它原本的可以用来放置内容的大小,被内边距给占用了,所以要减去
                     * 用一个例子来说,假设你有一个 Box,它的最大宽度是300dp,然后你决定添加50dp的 padding。那么,实际上,Box 内你能放置内容的最大宽度就变成了 300dp - 50dp*2 = 200dp。
                     * 相当于把 100dp 的空间给了 padding,真正用来放置内容的空间变小了。
                     */

                    maxWidth = constraints.maxWidth - (padding * 2).roundToPx(),
                    maxHeight = constraints.maxHeight - (padding * 2).roundToPx(),
                )
            )

            // 设置大小
            layout(placeable.width + (padding * 2).roundToPx(), placeable.height + (padding * 2).roundToPx()) {
                placeable.placeRelative(padding.roundToPx(), padding.roundToPx()) // 设置偏移
            }
        }) {        
        Text(text = "Hello World")
    }
}

这里我们给这个控件增加左右上下 10dp 的边距间隙,首先是需要通过 measurable.measure() 进行测量,这里我们由于给当前控件增加 10dp 的间隙,当前的控件可以真正可显示的内容就少了 10dp,所以在设置 constraints 约束条件的时候,需要减去 10 dp,为什么要乘以 2,是因为左右上下都有 padding,然后通过 layout 将结果返回,然后再通过 placeRelative 进行 padding 像素的偏移,最终运行代码的效果是这样的

上面我们是自己手写了一个 padding 的实现过程,其实,我们可以看看官方的 Compose 的源代码中,Modifier.padding 基本也是这样做的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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)) // 测量(减去padding)

        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())
            }
        }
    }
}

好了,基本上 layout 函数的使用大体就是如此。那么问题来了,设置 padding 像这样的,Compose 已经提供了,那 Modifier.layout 有什么使用场景呢?

能想到的一个场景有:是在原有的 Compose 组件内部已经都设置好了尺寸和位置了,而这个时候,我想额外设置这个组件的尺寸和偏移,比如如下例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Preview
@Composable
fun test() {
    Box(modifier = Modifier.layout { measurable, constraints ->
        val placeable = measurable.measure(constraints)
        layout(placeable.width, placeable.height) {
            placeable.placeRelative(30, 0)
        }
    }) {
        Box() {
            Column {
                Image(painter = painterResource(id = R.mipmap.aaa), contentDescription = "")
                Text(text = "hello world !!! ")
            }
        }
    }
}

这里内部组件的图片和文字的位置在实际的项目中,假设说是已经完美了,而这个时候,我想对这一整个整体进行偏移,那么可以使用 layout 函数进行设置,目前能想到的场景好像只有这个了,不过其实,单独设置 padding 也是可以做到的 关于 layout 这个函数,我觉得用到的场景是不多的,关键是要理解 layout 函数在测量过程中会对布局产生什么影响,有了之前的一些前奏知识,接下来我们再仔细看下它的测量流程是怎么样的

LayoutModifier 对布局产生的影响

在讲解 LayoutModifier 原理前先看下如下代码他们的组件大小是多少呢 ?

1
2
3
4
5
6
7
8
9
Box(Modifier.size(100.dp).size(200.dp))
Box(Modifier.size(200.dp).size(100.dp))
Box(
    modifier = Modifier
        .size(100.dp)
    	.background(Blue)
    	.size(50.dp)
    	.background(Origin)
)

LayoutNode 的测量过程

在讲解 LayoutModifier 之前,我们先要了解一个前置知识,那就是 Compose 在组合阶段,会将我们写的 Composable 函数最终组合成一个 LayoutNode 对象,而这个 LayoutNode 对象就是用来测量布局的,其中在 LayoutNode 类中测量和布局分别由 remeasure() 和 replace() 处理。

remeasure

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
layout.kt

internal val layoutDelegate = LayoutNodeLayoutDelegate(this)
	
internal val measurePassDelegate
get() = layoutDelegate.measurePassDelegate


internal fun remeasure(
	constraints: Constraints? = layoutDelegate.lastConstraints
): Boolean {
	return if (constraints != null) {
		if (intrinsicsUsageByParent == UsageByParent.NotUsed) {
			// This LayoutNode may have asked children for intrinsics. If so, we should
			// clear the intrinsics usage for everything that was requested previously.
			clearSubtreeIntrinsicsUsage()
		}
		measurePassDelegate.remeasure(constraints)  // 重要
	} else {
		false
	}
}

其中,可以看到通过 measurePassDelegate 对象调用 remeasure 方法,然后 measurePassDelegate 对象又是 LayoutNodeLayoutDelegate 中的 MeasurePassDelegate 的一个内部类,所以最终的调用者是在 MeasurePassDelegate 中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fun remeasure(constraints: Constraints): Boolean {
	...
	
	if (layoutNode.measurePending || measurementConstraints != constraints) {
		...
		performMeasure(constraints)
		...
		return sizeChanged
	} else {
		...
	}
	return false
}

然后再次调用 performMeasure

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
private fun performMeasure(constraints: Constraints) {
	layoutState = LayoutState.Measuring
	measurePending = false
	layoutNode.requireOwner().snapshotObserver.observeMeasureSnapshotReads(
		layoutNode,
		affectsLookahead = false
	) {
		outerCoordinator.measure(constraints)  // 重要
	}

	if (layoutState == LayoutState.Measuring) {
		markLayoutPending()
		layoutState = LayoutState.Idle
	}
}

在 performMeasure 中,又通过 outerCoordinator 调用了 measure 方法

1
2
3
interface Measurable : IntrinsicMeasurable {
    fun measure(constraints: Constraints): Placeable
}

最终可以看到, 在这里的调用链就结束了, 再深入是一个接口了, 所以这个时候我们应该找到 outerCoordinator 的实现类是什么了

1
2
3
4
5
6
internal class LayoutNodeLayoutDelegate(
    private val layoutNode: LayoutNode,
) {
    val outerCoordinator: NodeCoordinator
        get() = layoutNode.nodes.outerCoordinator
}

在这里可以看到, 这个 outerCoordinator 的获取方式是 layoutNode.nodes.outerCoordinator , 这个 layoutNode 我们上面说过了, 然后再看下 nodes 是什么

1
2
3
internal class LayoutNode(){
    internal val nodes = NodeChain(this)
}

然后再看下 NodeChain ,因为 outerCoordinator 就在这个类中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
internal class NodeChain(val layoutNode: LayoutNode) {
    // 负责最内层测量的 NodeCoordinator
    internal val innerCoordinator = InnerNodeCoordinator(layoutNode) // Composable 
    internal var outerCoordinator: NodeCoordinator = innerCoordinator
        private set
    internal val tail: Modifier.Node = innerCoordinator.tail
    internal var head: Modifier.Node = tail
        private set
    private val isUpdating: Boolean get() = head === SentinelHead
    private val aggregateChildKindSet: Int get() = head.aggregateChildKindSet
    private var current: MutableVector<Modifier.Element>? = null
    private var buffer: MutableVector<Modifier.Element>? = null
}

最终兜兜转转我们终于找到了 outerCoordinator 是个什么, 它是一个 NodeCoordinator 类型的对象, 他是用来测量和摆放布局的一个工具类, 这个时候,如果我们的组件一个 Modifier 都没有写的话, 获取的是 innerCoordinator , innerCoordinator 可以认为它是控件本身的一个测量布局的方式, 比如仅仅写一个 Text(),那他就是按照自身的测量规则

同时我们也可以发现 NodeChain 是一个双向链表, 因为它内部维护了 head tail 头尾指针, 到了这里, 我们可以大胆的猜测一些, 如果我们写了多个 LayoutModifier 那么是不是意味着都会被链在这个双向链表中呢 ?

确实是这样的, 在 Compose 组合阶段, 每一个 Composable 函数,都会被组合成一个 LayoutNode 对象, 然后它设置的 Modifier 会被包装成一个一个的 Modifier.Node 对象, 然后通过 NodeChain 串成一个双向链表 那么问题又来了, 我们设置的 Modifier 他是怎么被包装成 Modifier.Node 对象的呢 ? 又是怎么串成一个双向链表的呢 ?

其实答案在 LayoutNode 中的 Modifier 属性中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
override var modifier: Modifier = Modifier
    set(value) {
        field = value
        nodes.updateFrom(value) // 重点
        ...
    }


internal fun updateFrom(m: Modifier) {
    var before = current
    val beforeSize = before?.size ?: 0
    //  fillVector 会将 Modifier 展开铺平到一个数组,后面的代码就可以用这个数组遍历
    val after = m.fillVector(buffer ?: mutableVectorOf())
    if (after.size == beforeSize) {
        var node: Modifier.Node? = paddedHead.child
        while (node != null && i < beforeSize) {
            val prev = before[i]
            val next = after[i]
            when (actionForModifiers(prev, next)) {
                    ...
            }
            // NOTE: We do not need to check if the node is attached since these are all updated
            // or reused modifiers only
            node = node.child
            i++
        }
        if (i < beforeSize) {
            coordinatorSyncNeeded = true
            structuralUpdate
                i,
                before,
                after,
                node,
                layoutNode.isAttached,
            )
        }
    } else if (!layoutNode.isAttached && beforeSize == 0) {
        // 第一次组装双向链表
        // 遍历上面铺平的数组,然后将 Modifier 装进 Node 组装成双向链表
        coordinatorSyncNeeded = true
        var node = paddedHead
        while (i < after.size) {
            val next = after[i]
            val parent = node
            // 组装双向链表的具体逻辑
            node = createAndInsertNodeAsChild(next, parent)
            logger?.nodeInserted(0, i, next, parent, node)
            i++
        }
        syncAggregateChildKindSet()
    } else if (after.size == 0) {
        checkNotNull(before) { "expected prior modifier list to be non-empty" }
        // common case where we we are removing all the modifiers.
        var node = paddedHead.child
        while (node != null && i < before.size) {
            logger?.nodeRemoved(i, before[i], node)
            node = detachAndRemoveNode(node).child
            i++
        }
        innerCoordinator.wrappedBy = layoutNode.parent?.innerCoordinator
        outerCoordinator = innerCoordinator
    } else {
        ...
    }
    current = after
    
    buffer = before?.also { it.clear() }
    // 将头节点和尾节点保存到 head 和 tail
    head = trimChain(paddedHead)
    if (coordinatorSyncNeeded) {
        //  将 Node 和所属的 NodeCoordinator 挂接关联
        syncCoordinators()
    }
}

这一步的逻辑是当我们在外部设置 Modifier 的时候, 那么就会调用到这里的 Modifier 的 set 函数, 然后调用 nodes.updateFrom(value) , 这个 value 就是我们组件设置的 Modifier, 一般来说多个的话,肯定是一个 CombinedModifier(这个我们之前的文章有提过),然后在 updateFrom 的内部,会通过 fillVector 方法,将我们外部设置的 Modifier 链铺平, 假设我们在外部设置的是 Modifier.size().padding().layout() 那么这里铺平后的集合就是这样的 sizeNode , paddingNode LayoutModifierNode

然后将铺平后的数据, 会通过 node = createAndInsertNodeAsChild(next, parent) 方法,将 nodeChain 这个双向链表给连起来, 最后也是比较重要的一步, 就是通过 syncCoordinators 方法,将设置好的 node 关联一个 NodeCoordinator 对象, 这个 NodeCoordinator 对象我们之前提到过, 就是用来测量布局的工具类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    fun syncCoordinators() {
        var coordinator: NodeCoordinator = innerCoordinator
        var node: Modifier.Node? = tail.parent
        while (node != null) {
            val layoutmod = node.asLayoutModifierNode()
            if (layoutmod != null) {
                val next = if (node.coordinator != null) {
                    val c = node.coordinator as LayoutModifierNodeCoordinator
                    val prevNode = c.layoutModifierNode
                    c.layoutModifierNode = layoutmod
                    if (prevNode !== node) c.onLayoutModifierNodeChanged()
                    c
                } else {
                    val c = LayoutModifierNodeCoordinator(layoutNode, layoutmod) // 创建 LayoutModifierNodeCoordinator
                    node.updateCoordinator(c)
                    c
                }
                coordinator.wrappedBy = next
                next.wrapped = coordinator
                coordinator = next
            } else {
                node.updateCoordinator(coordinator)
            }
            node = node.parent
        }
        coordinator.wrappedBy = layoutNode.parent?.innerCoordinator
        outerCoordinator = coordinator
    }

这里的步骤就是给 node 对象设置一个 NodeCoordinator 对象,让每一个 LayoutModifier 都具备测量和布局的能力

到了这里我们之前分析的 outerCoordinator.measure(constraints),已经可以知道这个 outerCoordinator 就是一个 LayoutModifierNodeCoordinator 对象了,那么我们再来看下它的 measure 测量方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    override fun measure(constraints: Constraints): Placeable {
        performingMeasure(constraints) {
            with(layoutModifierNode) {
                measureResult = if (this is IntermediateLayoutModifierNode) {
                    intermediateMeasure(
                        wrappedNonNull,
                        constraints,
                        lookaheadDelegate!!.measureResult.let { IntSize(it.width, it.height) },
                        lookaheadConstraints!!
                    )
                } else {
                    measure(wrappedNonNull, constraints) // 最终调用的地方
                }
                this@LayoutModifierNodeCoordinator
            }
        }
        onMeasured()
        return this
    }
1
2
3
4
5
6
7
// LayoutModifierNode.kt 
interface LayoutModifierNode : DelegatableNode {
    fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult
}

而这里调用的 measure 方法就是所属于 LayoutModifierNode 类中的方法了, 而 LayoutModifierNode 就是我们这是 padding,设置 size 的时候,它的内部方法实现在这个地方

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
SizeNode 

@Stable
fun Modifier.size(size: Dp) = this.then(
    SizeElement( )
)

private class SizeElement(
    private val minWidth: Dp = Dp.Unspecified,
    private val minHeight: Dp = Dp.Unspecified,
    private val maxWidth: Dp = Dp.Unspecified,
    private val maxHeight: Dp = Dp.Unspecified,
    private val enforceIncoming: Boolean,
    private val inspectorInfo: InspectorInfo.() -> Unit
) : ModifierNodeElement<SizeNode>() {
    override fun create(): SizeNode =
        SizeNode(
            minWidth = minWidth,
            minHeight = minHeight,
            maxWidth = maxWidth,
            maxHeight = maxHeight,
            enforceIncoming = enforceIncoming
        )

    override fun update(node: SizeNode) {
        node.minWidth = minWidth
        node.minHeight = minHeight
        node.maxWidth = maxWidth
        node.maxHeight = maxHeight
        node.enforceIncoming = enforceIncoming
    }
}


private class SizeNode(
    var minWidth: Dp = Dp.Unspecified,
    var minHeight: Dp = Dp.Unspecified,
    var maxWidth: Dp = Dp.Unspecified,
    var maxHeight: Dp = Dp.Unspecified,
    var enforceIncoming: Boolean
) : LayoutModifierNode, Modifier.Node() {

    override fun MeasureScope.measure( // sizeNode 的测量布局方式
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult {
        val wrappedConstraints =  ....
        val placeable = measurable.measure(wrappedConstraints)
        return layout(placeable.width, placeable.height) {
            placeable.placeRelative(0, 0)
        }
    }
}

// paddingNode
@Stable
fun Modifier.padding(all: Dp) = this then PaddingElement()


private class PaddingElement(
    var start: Dp = 0.dp,
    var top: Dp = 0.dp,
    var end: Dp = 0.dp,
    var bottom: Dp = 0.dp,
    var rtlAware: Boolean,
    val inspectorInfo: InspectorInfo.() -> Unit
) : ModifierNodeElement<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( // // PaddingNode 的测量布局方式
        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())
            }
        }
    }
}

我们可以写一个更加简单的例子,可以更加清晰的看到这个过程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
fun layoutModifier2() {
    BoxWithConstraints(modifier = Modifier
        .layout { measurable, constraints ->
            println("@@@@ <top>.layoutModifier2  流程1")

            val placeable = measurable.measure(constraints)
            
            println("@@@@ <top>.layoutModifier2  流程2")

            // 偏移
            layout(placeable.width, placeable.height) {
                placeable.placeRelative(0, 0)
            }
        }
        .layout { measurable, constraints ->
            println("@@@@ <top>.layoutModifier2  流程3")
            
            val placeable = measurable.measure(constraints)

            println("@@@@ <top>.layoutModifier2  流程4")
            layout(placeable.width, placeable.height) {
                placeable.placeRelative(0, 0)
            }
        }) {
        Text(text = "Hello World")
    }

}

它的打印结果是这样的

1
2
3
4
@@@@ <top>.layoutModifier2  流程1
@@@@ <top>.layoutModifier2  流程3
@@@@ <top>.layoutModifier2  流程4
@@@@ <top>.layoutModifier2  流程2

所以这个流程应该这样就很清楚了,有点像一个链式调用,其实也有点像 okHttp 中的 process 的处理方式,在调用第一个 layout 进行测量的时候, 它会将约束条件传递到下一个 layout 函数中,而下一个 layout 函数,因为没有后续的 LayoutModifier 了,所以就不会再往内层传递了,而是测量完成后,将测量结果大小再一层一层的返回(如果左边的约束条件更加严格的话,则右边的尺寸将受到左边的约束)

到了这里,我们再来看看上面说的, 它们的结果是什么了

1
2
3
4
5
6
7
8
9
Box(Modifier.size(100.dp).size(200.dp)) // 最终大小为 100dp
Box(Modifier.size(200.dp).size(100.dp)) // 最终大小为 200dp
Box(
    modifier = Modifier
        .size(100.dp)
    	.background(Blue)
    	.size(100.dp)
    	.background(Red)  // 大小为 100dp 的红色,盖着 100dp 的绿色 
)

总结

通过上面的两个例子和原理的分析,在日常编写程序时我们就可以简单理解 LayoutModifier 对测量布局的影响是:当 Composable 有多个 LayoutModifier 时,最终会以最外层的 LayoutModifier 为最终结果(即是最左边的 LayoutModifier),最外层的优先级是最高的