深度解析 Compose 的 Modifier 原理 -- Modifier.layout()、LayoutModifier

发布时间:2024年01月22日

在这里插入图片描述


"Jetpack Compose - - Modifier 原理系列文章 "


????📑 《 深度解析 Compose 的 Modifier 原理 - - Modifier、CombinedModifier 》

????📑 《 深度解析 Compose 的 Modifier 原理 - - Modifier.composed()、ComposedModifier 》

????📑 《 深度解析 Compose 的 Modifier 原理 - - Modifier.layout()、LayoutModifier 》

????📑 《 深度解析 Compose 的 Modifier 原理 - - DrawModifier 》

????📑 《 深度解析 Compose 的 Modifier 原理 - - PointerInputModifier 》

????📑 《 深度解析 Compose 的 Modifier 原理 - - ParentDataModifier 》


在正式开始分析 LayoutModifier 相关原理之前,建议你先看看 Compose 是如何将数据转换成 UI 的?这篇文章,当你了解了 Compose 的“组合”、“布局”、“绘制”的思维模型后,有助于你更透彻的了解 Modifier 的底层设计原理。如果此时你对 Modifier 还不了解,可以先阅读前面两篇关于 Modifier 的文章,这些都是必备基础。

一、场景引入

1.1 一个 Text()

先来看一个最简单的代码示例:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green)) {
                    Text("Hi Compose")
                }
            }
        }
    }
}

这段代码很简单,效果图如下:

在这里插入图片描述

接下来基于这个 Demo,我们会慢慢引入本篇文章的主角。

1.2 Modifier.layout()

在 Compose 中 Modifier.layout() 是一种布局修饰符,它会包裹一个布局节点:Layout(什么是 LayoutNode,下面会讲),通常用作对目标组件进行测量和位置摆放的。

说白了就是你可以用 Modifier.layout() 来自定义目标组件的测量过程以及决定目标组件怎么摆放。

现在我们看看代码中怎么用,通常会像下面这样写:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green)) {
                    Text("Hi Compose",
                        Modifier.layout { measurable, constraints ->  }
                    )
                }
            }
        }
    }
}

就这么简单,Text 就是目标组件,我们给它加了一个 Modifier.layout(),没有添加任何其他代码逻辑,{ measurable, constraints -> } 是自动生成的,此时你会发现在 Android Studio IDLE 中,这样写是会标红的:

在这里插入图片描述

正常来说,我这里什么也不填不就相当于对 Text() 不做任何修饰。但很明显这样是不行的,接下来我们一起尝试解决这个报错。

首先我们发现当使用 layout() 修饰符时,传入的回调 lambda 包含了两个参数:

  1. measurable:用于子元素的测量和位置放置;
  2. constraints:用于约束子元素 width 和 height 的最大值和最小值。

我们定位到 Modifier.layout() 的源码:

fun Modifier.layout(
    measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) = this then LayoutModifierElement(measure)

measurable 对应的是 Measurable:

interface Measurable : IntrinsicMeasurable {
    // 返回一个 Placeable,它里面包含目标组件的宽、高等信息
    fun measure(constraints: Constraints): Placeable
}

Measurable 是一个接口,内部仅有一个 measure() 方法。

所以现在可以开始修改刚才的报错了:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green)) {
                    Text("Hi Compose",
                        Modifier.layout { measurable, constraints ->
                            // 用一个变量保存返回的 Placeable 对象
                            val placeable = measurable.measure(constraints)
                        }
                    )
                }
            }
        }
    }
}

现在代码仍然是标红报错的,原因在于:我们只处理了 measurable,它返回的是 Placeable,而 Modifier.layout() 需要返回的类型是 MeasureResult

fun Modifier.layout(
    measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) = this then LayoutModifierElement(measure)

所以 MeasureResult 是什么?

interface MeasureResult {
    val width: Int
    val height: Int
    val alignmentLines: Map<AlignmentLine, Int>
    fun placeChildren()
}

MeasureResult 也是一个接口,它里面也有 widthheight,继续修复刚才的报错:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBlogTheme {
                Text("ComposeTest",
                    Modifier.layout { measurable, constraints ->
                        val placeable = measurable.measure(constraints)
                        object : MeasureResult {
                            override val alignmentLines: Map<AlignmentLine, Int>
                                get() = TODO("Not yet implemented")
                            override val height: Int
                                get() = TODO("Not yet implemented")
                            override val width: Int
                                get() = TODO("Not yet implemented")

                            override fun placeChildren() {
                                TODO("Not yet implemented")
                            }
                        }
                    }
                )
            }
        }
    }
}

既然 Modifier.layout() 需要一个 MeasureResult 返回对象,那我们就在内部给它创建一个 MeasureResult 对象,此时 IDLE 就不会再报错了。

当然我们还需要做一个工作,那就是把 placeable 的宽高传进 MeasureResult 内部,所以最终的代码修改如下:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green)) {
                    Text("Hi Compose",
                        Modifier.layout { measurable, constraints ->
                            val placeable = measurable.measure(constraints)
                            object : MeasureResult {
                                // 测量基准线,暂时不用关心
                                override val alignmentLines: Map<AlignmentLine, Int>
                                    get() = TODO("Not yet implemented")
                                // 高:placeable.height
                                override val height: Int
                                    get() = placeable.height
                                // 宽:placeable.width
                                override val width: Int
                                    get() = placeable.width

                                // 摆放内部组件,暂时不用关心
                                override fun placeChildren() {
                                    TODO("Not yet implemented")
                                }
                            }
                        }
                    )
                }
            }
        }
    }
}

至此报错就修复了,上面的代码演示了对 Text() 添加 Modifier.layout() 进行修饰(当然上面的做法等同于啥也没做)。

但这段代码有个缺陷:如果每次通过 Modifier.layout() 对组件修饰,都得像上面这样写一堆代码,那还不得疯?

1.3 layout() 函数

其实在实际开发中我们并不会这么写,而是使用 Compose 提供给我们的 layout() 函数

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green)) {
                    Text("Hi Compose",
                        Modifier.layout { measurable, constraints ->
                            val placeable = measurable.measure(constraints)
                            /*object : MeasureResult {
                                override val alignmentLines: Map<AlignmentLine, Int>
                                    get() = TODO("Not yet implemented")
                                override val height: Int
                                    get() = TODO("Not yet implemented")
                                override val width: Int
                                    get() = TODO("Not yet implemented")

                                override fun placeChildren() {
                                    TODO("Not yet implemented")
                                }

                            }*/
                            layout() {
                                
                            }
                        }
                    )
                }
            }
        }
    }
}

我们来看下 layout() 源码:

    fun layout(
        width: Int,
        height: Int,
        alignmentLines: Map<AlignmentLine, Int> = emptyMap(),
        placementBlock: Placeable.PlacementScope.() -> Unit
    ) = object : MeasureResult {  // 看这里,这是个啥?熟悉吗?
        override val width = width
        override val height = height
        override val alignmentLines = alignmentLines
        override fun placeChildren() {
            Placeable.PlacementScope.executeWithRtlMirroringValues(
                width,
                layoutDirection,
                this@MeasureScope as? LookaheadCapablePlaceable,
                placementBlock
            )
        }
    }

很明显 layout() 函数帮我们创建好了 MeasureResult 对象,同时它还帮我们干了另外两件没做的事:

  1. 给 alignmentLines 设定了默认值;
  2. 实现了 placeChildren()。

所以,我们现在只需要补全 layout() 函数剩余的两个参数:widthheight

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green)) {
                    Text("Hi Compose",
                        Modifier.layout { measurable, constraints ->
                            val placeable = measurable.measure(constraints)
                            layout(placeable.width, placeable.height) {

                            }
                        }
                    )
                }
            }
        }
    }
}

这样写代码是不是瞬间感觉清爽了很多?但工作到这边还没有结束,layout() 函数还有第四个参数,是一个 Lambda 表达式,主要工作是处理被修饰组件的摆放规则,比如偏移量。

fun layout(
    width: Int,
    height: Int,
    alignmentLines: Map<AlignmentLine, Int> = emptyMap(),
    placementBlock: Placeable.PlacementScope.() -> Unit    // Lambda 表达式
) = object : MeasureResult {
... ...
}

我们继续完善:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green)) {
                    Text("Hi Compose",
                        Modifier.layout { measurable, constraints ->
                            val placeable = measurable.measure(constraints)
                            layout(placeable.width, placeable.height) {
                                // 不做任何偏移
                                placeable.placeRelative(0, 0)
                            }
                        }
                    )
                }
            }
        }
    }
}

现在所有工作(测量 + 摆放)都已完成,运行看下效果:

在这里插入图片描述

可以看出来,没有任何变化,因为我们虽然用 Modifier.layout() 对 Text 做修饰,但并没有对它做任何尺寸修改和位置偏移。

那如果我现在想修改 Text() 的尺寸,该怎么做?

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green)) {
                    Text("Hi Compose",
                        Modifier.layout { measurable, constraints ->
                            val placeable = measurable.measure(constraints)
                            val size = min(placeable.width, placeable.height)
                            layout(size, size) {
                                placeable.placeRelative(0, 0)
                            }
                        })
                }
            }
        }
    }
}

我们定义了一个 size 变量,通过 min() 函数获取宽高最小值,然后重新传入 layout() 里面,这样就会获得一个正方形的效果。

在这里插入图片描述

尺寸修改确实生效了,接下来再增加一个偏移:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green)) {
                    Text("Hi Compose",
                        Modifier.layout { measurable, constraints ->
                            val placeable = measurable.measure(constraints)
                            val size = min(placeable.width, placeable.height)
                            layout(size, size) {
                                placeable.placeRelative(10, 0)
                            }
                        })
                }
            }
        }
    }
}

看下效果:

在这里插入图片描述

另外有个细节需要说明下,除了使用 placeRelative 对组件偏移外,也可以使用 place 进行偏移操作,两者的区别就是 placeRelative 会自适应 RTL 布局。

讲到这里,Modifier.layout() 修饰符和 layout() 函数的用法你应该都清楚了,但还没结束,前面我们一直忽略了一个参数:constraints,它是什么?

  1. measurable:用于子元素的测量和位置放置的;
  2. constraints:用于约束子元素 width 和 height 的最大值和最小值。

前面的例子并没有对 constraints 做任何修改,在实际开发过程中,我们往往需要通过 constraints 对组件进行限制。

比如我想对 Text() 组件进行一个限制,类似 padding 的效果,给它加一个 10dp 的最大宽高的限制(最大宽高缩减 10dp)。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green)) {
                    Text("Hi Compose",
                        Modifier.layout { measurable, constraints ->
                            val paddingPx = 10.dp.roundToPx()
                            val placeable = measurable.measure(constraints.copy(
                                maxWidth = constraints.maxWidth - paddingPx * 2,
                                maxHeight = constraints.maxHeight - paddingPx * 2
                            ))
                            layout(placeable.width + paddingPx * 2, placeable.height + paddingPx * 2) {
                                placeable.placeRelative(paddingPx, paddingPx)
                            }
                        })
                }
            }
        }
    }
}

看下效果:

在这里插入图片描述

很明显,我们实现的效果跟 Modifier.padding(10.dp) 的效果是一样的,如果你去看看 Modifier.padding 的源码,就会发现它的内部原理跟我们例子是一样的。

@Stable
fun Modifier.padding(
    horizontal: Dp = 0.dp,
    vertical: Dp = 0.dp
) = this.then(
    PaddingModifier(
        ... ...
    )
)
private class PaddingModifier(
    val start: Dp = 0.dp,
    val top: Dp = 0.dp,
    val end: Dp = 0.dp,
    val bottom: Dp = 0.dp,
    val rtlAware: Boolean,
    inspectorInfo: InspectorInfo.() -> Unit
) : LayoutModifier, InspectorValueInfo(inspectorInfo) {
    ... ...

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

二、LayoutNode 浅析


前面所有的示例代码,不论是使用 Modifier.layout() 修饰符还是使用 Compose 提供给我们的现成的修饰符,比如:Modifier.padding() / Modifier.size(),它们都会对被修饰组件产生精细影响(组件大小、位置偏移)。

但到目前为止,我们仅仅是从 UI 效果上看到 Modifier.layout() 会影响被修饰组件,但源码底层是如何产生影响的呢?这才是我们这篇文章的核心!

所以,最硬核的原理部分来了!

我们就拿常用的 Modifier.padding() 分析:

@Stable
fun Modifier.padding(
    horizontal: Dp = 0.dp,
    vertical: Dp = 0.dp
) = this.then(
    PaddingModifier(
        ... ...
    )
)

private class PaddingModifier(
    ... ...
) : LayoutModifier, InspectorValueInfo(inspectorInfo) {


interface LayoutModifier : Modifier.Element {

Modifier.padding() 内部会调用一个 PaddingModifier 对象,而 PaddingModifier 实现了 LayoutModifier 接口,这个 LayoutModifier 会被 Compose 用于修改测量和布局过程,从而最终影响到界面元素的位置和尺寸。

所以我们的重点就是要研究 LayoutModifier 是如何影响组件的


但是!在分析 LayoutModifier 原理之前,有一个核心知识点是必须要提前了解的。

2.1 一个 Text()

这段代码我们再熟悉不过了:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green)) {
                    Text("Hi Compose")
                }
            }
        }
    }
}

Box 、Text 这些函数在实际运行的时候,其实并不是这些函数直接存在于内存里面,而是 Compose 利用这些函数创造出的一些对象存在于内存里面,这个对象就是:LayoutNode,它才是最底层的那个节点,进行实际的测量、布局、绘制、触摸反馈等工作,你可以查看 Compose 是如何将数据转换成 UI 的?这篇文章,了解转换的思维模型!

我们既然想知道 LayoutModifier 是如何精细影响 Text() 组件,那就得先研究明白 Text() 自己的测量、布局、绘制的原理,因为 LayoutModifier 是包着这个 Text() 的。

在 LayoutNode 中,测量和布局是由 remeasure() 函数和 replace() 两个函数处理。

// LayoutNode.kt
internal class LayoutNode(..) {
    internal fun replace()      // 布局
    internal fun remeasure()    // 测量
}

2.2 LayoutNode.remeasure()

我们先来分析 remeasure() 函数:

// LayoutNode.kt
internal class LayoutNode(..) {

    internal fun remeasure(
        constraints: Constraints? = layoutDelegate.lastConstraints
    ): Boolean {
        return if (constraints != null) {
            if (intrinsicsUsageByParent == UsageByParent.NotUsed) {
                clearSubtreeIntrinsicsUsage()
            }
            // 测量工作交给 LayoutNodeLayoutDelegate 的内部类 MeasurePassDelegate 处理
            measurePassDelegate.remeasure(constraints)
        } else {
            false
        }
    }

}

2.3 MeasurePassDelegate.measurePassDelegate()

LayoutNode 内部要处理的事情非常多,它把测量的工作又交给了 MeasurePassDelegate 来处理。

// LayoutNodeLayoutDelegate.kt
internal class LayoutNodeLayoutDelegate(
    private val layoutNode: LayoutNode
) {

	inner class MeasurePassDelegate : Measurable, Placeable(), AlignmentLinesOwner {
	
	    fun remeasure(constraints: Constraints): Boolean {
	        ...
	        if (layoutNode.measurePending || measurementConstraints != constraints) {
	            ...
	            performMeasure(constraints)    // 关键代码
	            ...
	        }
	        return false
	    }
	
	}

}

这段代码很长,但我们只需要关注一行关键代码:performMeasure(constraints)

2.4 performMeasure()

// LayoutNodeLayoutDelegate.kt
internal class LayoutNodeLayoutDelegate(
    private val layoutNode: LayoutNode
) {

    private fun performMeasure(constraints: Constraints) {
        ...
        layoutNode.requireOwner().snapshotObserver.observeMeasureSnapshotReads(
            layoutNode,
            affectsLookahead = false
        ) {
            outerCoordinator.measure(constraints)    // 关键代码
        }
        ...
    }

}

我们仍然只需要关注:outerCoordinator.measure(constraints),它是做实际测量工作的。

2.5 Measurable.measure()

// Measurable.kt
interface Measurable : IntrinsicMeasurable {
    /**
     * Measures the layout with [constraints], returning a [Placeable] layout that has its new
     * size. A [Measurable] can only be measured once inside a layout pass.
     */
    fun measure(constraints: Constraints): Placeable
}

???怎么是个接口啊,没有任何处理逻辑啊。

那肯定有其他地方实现了这个方法,我们可以搜一下哪些地方实现了。

在这里插入图片描述

有这么多地方实现了,但哪一个才是我们需要的?别慌,我带你找一下。

我们往回退,刚才哪里调用 measure 的?

// LayoutNodeLayoutDelegate.kt
internal class LayoutNodeLayoutDelegate(
    private val layoutNode: LayoutNode
) {
    /**
     * 2. outerCoordinator 由传进来的 layoutNode 参数决定,那么我们得找找 layoutNode 是哪里传进来的
     *    这里记住:
     *    a. 接下来我们先找到哪里传入了 layoutNode
     *    b. 找到后我们再看 layoutNode.nodes.outerCoordinator 是什么?
     */
    val outerCoordinator: NodeCoordinator
        get() = layoutNode.nodes.outerCoordinator
    
    private fun performMeasure(constraints: Constraints) {
        ...
        layoutNode.requireOwner().snapshotObserver.observeMeasureSnapshotReads(
            layoutNode,
            affectsLookahead = false
        ) {
            // 1. 这里调用了 measure(),那么 outerCoordinator 是什么?
            outerCoordinator.measure(constraints)
        }
        ...
    }

}

继续回退到上一层:

// LayoutNodeLayoutDelegate.kt
internal class LayoutNodeLayoutDelegate(
    private val layoutNode: LayoutNode
) {

	inner class MeasurePassDelegate : Measurable, Placeable(), AlignmentLinesOwner {
	
	    // 2. 我们继续回退
	    fun remeasure(constraints: Constraints): Boolean {
	        ...
	        if (layoutNode.measurePending || measurementConstraints != constraints) {
	            ...
	            // 1. 没有地方传入 layoutNode 啊
	            performMeasure(constraints)
	            ...
	        }
	        return false
	    }
	
	}

}

继续回退到上一层:

// LayoutNode.kt
internal class LayoutNode(..) {

    // 2. 通过 layoutDelegate 获取 measurePassDelegate,那 layoutDelegate 是什么?
    private val measurePassDelegate
        get() = layoutDelegate.measurePassDelegate
    ...
    
    // 4. 来来来,nodes 在这里,NodeChain 又是什么?
    internal val nodes = NodeChain(this)

    /**
     * 3. LayoutNodeLayoutDelegate(this) 看到这行代码你有没有想起来什么?
     *    这个 this 不就是我们要找的那个 layoutNode 参数嘛!
     *    现在我们再看下刚刚用到这个参数的地方:
     *    // 还记得吧?现在 layoutNode 找到了,接下来看看 nodes 是什么?
     *    val outerCoordinator: NodeCoordinator
     *        get() = layoutNode.nodes.outerCoordinator
     */
    internal val layoutDelegate = LayoutNodeLayoutDelegate(this)
    internal val outerCoordinator: NodeCoordinator
        get() = nodes.outerCoordinator

    internal fun remeasure(
        constraints: Constraints? = layoutDelegate.lastConstraints
    ): Boolean {
        return if (constraints != null) {
            ...
            // 1. measurePassDelegate 是什么?
            measurePassDelegate.remeasure(constraints)
        } else {
            false
        }
    }

}

2.6 NodeChain

我们再来看看 NodeChain 是什么?

// NodeChain.kt
internal class NodeChain(val layoutNode: LayoutNode) {
    // 2. innerCoordinator 又是 InnerNodeCoordinator!
    internal val innerCoordinator = InnerNodeCoordinator(layoutNode)
    // 1. layoutNode.nodes.outerCoordinator 是 innerCoordinator
    internal var outerCoordinator: NodeCoordinator = innerCoordinator
        private set
    ... ...
}

代码跟踪到这里就真相显现了,到底是哪个实现类处理了 measure() 方法,你现在清楚了吧?再来看下刚刚的截图:

在这里插入图片描述

2.7 InnerNodeCoordinator.measure()

所以接下来我们就看看 InnerNodeCoordinator 是如何负责具体测量的:

// InnerNodeCoordinator.kt
internal class InnerNodeCoordinator(
    layoutNode: LayoutNode
) : NodeCoordinator(layoutNode) {

    override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
        layoutNode.forEachChild {
            it.measuredByParent = LayoutNode.UsageByParent.NotUsed
        }

        // 2. 返回一个 MeasureResult 对象给 replace() 去布局
        measureResult = with(layoutNode.measurePolicy) {
            // 1. 最核心处:这边就是最底层开始测量的工作了
            measure(layoutNode.childMeasurables, constraints)
        }
        onMeasured()
        return this
    }

}

分析到这里,关于组件的测量和布局流程就跑通了: 我们在代码中所写的 Box、Text 等组件内部会有自己设定的测量数据,他们在代码实际运行过程中会被 Comopse 转换成 LayoutNode 节点(包含所有组件自身的测量数据),然后一层层往下传,最终传到 InnerNodeCoordinator,由它进行最底层的测量工作,测量完成后会返回一个 MeasureResult 对象再交给 replace() 函数完成布局工作。

所以,Do you understand?

在这里插入图片描述


三、LayoutModifer 的工作原理


前面我们已经了解了组件自身的测量和布局原理,现在就可以开始分析 LayoutModifer 是如何影响组件的测量和布局了。

就像我们前面说的那样,所有组件最终都会被转换为一个 LayoutNode,这个 LayoutNode 包含了所有的测量数据,那同样它也会包含你对组件设定的 Modifier,所以最终经过一些列转换,也会传到 LayoutNode 里面,那 LayoutNode 里面必然会存在一个 modifier 属性来处理你所设定的 Modifer.xx,我们来看源码内部是怎么处理的。

3.1 LayoutNode -> modifier

在 LayoutNode 中确实也有一个 modifier 属性来处理所有外部传入的 Modifier,代码如下:

// LayoutNode.kt
internal class LayoutNode(..) {

    // 可以理解该 modifier 是 Composable 所有 modifier 的组合
    override var modifier: Modifier = Modifier
        // 如果有新值变化
        set(value) {
            ...
            nodes.updateFrom(value)    // 关键代码
            ...
        }
}

这里的 nodes 应该不陌生了吧?前一节我们已经知道了它是 NodeChain 对象。

internal val nodes = NodeChain(this)

但是 NodeChain 是什么?其实它就是一个链表,而且是个双向链表。

// NodeChain.kt
internal class NodeChain(val layoutNode: LayoutNode) {
    internal val innerCoordinator = InnerNodeCoordinator(layoutNode)
    internal var outerCoordinator: NodeCoordinator = innerCoordinator
        private set
    internal val tail: Modifier.Node = innerCoordinator.tail
    internal var head: Modifier.Node = tail
        private set
    ...
}

头、尾节点都是 Modifier.Node 类型!其中的 NodeCoordinator 是用来辅助 Node 节点处理测量和布局的,其中包含 measure 和 placeAt 的函数逻辑。NodeChain链表上的每一个 Node 都会对应的绑定一个 NodeCoordinator 对象来辅助处理。

3.2 updateFrom()

接下来看看 updateFrom 的具体工作,它主要负责 NodeChain 链表的更新,每当有 Modifier 对象被设置到 LayoutNode 上面,都会调用 updateFrom 函数进行更新对应的 NodeChain。

// NodeChain.kt
internal class NodeChain(val layoutNode: LayoutNode) {
    // 负责最内层测量的 NodeCoordinator
    internal val innerCoordinator = InnerNodeCoordinator(layoutNode)
    // 负责外层测量的 NodeCoordinator,初始值是 InnerNodeCoordinator
    internal var outerCoordinator: NodeCoordinator = innerCoordinator
        private set
    // 双向链表的尾节点
    internal val tail: Modifier.Node = innerCoordinator.tail
    // 双向链表的头节点
    internal var head: Modifier.Node = tail
        private set
    ...
    
	internal fun updateFrom(m: Modifier) {
	    ...

        val before = current ?: MutableVector(capacity = 0)
        // 核心代码 1: fillVector 会将 Modifier 展开铺平到一个数组,后面的代码就可以用这个数组遍历
        val after = m.fillVector(buffer ?: mutableVectorOf())
        if (after.size == before.size) {
            ... // 这段代码不用看,只要加了 Modifier.xx 默认不会走到这边
        } else if (before.size == 0) {
            // 第一次组装双向链表
			// 遍历上面铺平的数组,然后将 Modifier 装进 Node 组装成双向链表
            attachNeeded = true
            coordinatorSyncNeeded = true
            var i = after.size - 1
            var aggregateChildKindSet = 0
            var node = tail
            while (i >= 0) {
                val next = after[i]
                val child = node
                // 核心代码 2: 组装双向链表的具体逻辑
                node = createAndInsertNodeAsParent(next, child)
                ...
                i--
            }
        }
        ...

        // 将头节点和尾节点保存到 head 和 tail
        trimChain()

        // 将 Node 和所属的 NodeCoordinator 挂接关联
        if (coordinatorSyncNeeded) {
            syncCoordinators()
        }
        ...
	}
}

接下来跟着我深入分析每一行核心代码!希望你能沉下心看下去,肯定能看懂!

🖇? 分解 Modifier

先来看第一个核心代码:

val after = m.fillVector(buffer ?: mutableVectorOf())

m 就是你的 Modifier 链,这里的 buffer 默认为 null,mutableVectorOf() 其实跟 buffer 一样都是一个可变列表,只是容量不同而已,你只要知道它就是一个列表,用来装分解后的单个 Modifier 用的就行了。

接着我们开始分析 fillVector 函数,仔细看注释!

// NodeChain.kt
private fun Modifier.fillVector(
    result: MutableVector<Modifier.Element>
): MutableVector<Modifier.Element> {
    // 1. 创建一个元素为 Modifier 的可变列表,并在初始化后添加传进来的 Modifier,
    // 从 stack 名字就可以看出来,这里相当于创建了一个栈
    val stack = MutableVector<Modifier>(result.size).also { it.add(this) }
    // 2. 只要栈不为空,一直循环
    while (stack.isNotEmpty()) {
        // 3. 从栈中移除并获取最后一个元素,存到 next 变量中
        when (val next = stack.removeAt(stack.size - 1)) {
            // 4. 如果 next 是 CombinedModifier 类型
            is CombinedModifier -> {
                stack.add(next.inner)  // 把 inner 部分加入到 stack 中
                stack.add(next.outer)  // 把 outer 部分加入到 stack 中
            }
            // 5. 如果 next 是 Modifier.Element 类型,则直接加到 result 中
            is Modifier.Element -> result.add(next)
            // 6. 如果 next 是 Modifier 的其他实现,可能我们不知道具体实现细节。
            // 把 it(可能是 Modifier.Element 的实例)加入到 result 中
            else -> next.all {
                result.add(it)
                true
            }
        }
    }
    // 7. 在处理完所有的元素之后,返回 result,它包含了所有的 Modifier.Element
    return result
}

这个函数其实就是添加了一个 Modifier 调用栈,以便可以迭代地处理一个可能嵌套的 Modifier 结构。栈的使用是为了在不使用递归的情况下扁平化修饰符的结构。

注意!!!从 1.3.0-beta01 版本开始,Compose 中不再使用 foldIn/foldOut 函数对 Modifier 进行遍历了,在 1.3.0-beta01 之前的版本 LayoutNode 源码中是通过 foldOut 遍历 + 头插法处理,而现在是通过 fillVector 函数处理达到类似的效果。

看的懂吗?是不是很懵?我们来个实际例子吧,比如传入的 Modifier 链如下:

// 这是你写的代码
Box(Modifier.padding(10.dp).size(20.dp))

// 此时 Modifier 链为:
//     modifierChain = Modifier.padding(10.dp).size(20.dp)
//
// 实际的结构:
//     CombinedModifier(
//        PaddingModifier  // outer
//        SizeModifier     // inner
//     )

我们来看看 fillVector 是怎么处理它的:

????1. 当调用 fillVector 函数时,初始状态是 modifierChain 放入了一个空的 stack 中。

????2. 此时 stack 不为空,开始循环:

????????· removeAt 从 stack 的末尾(最后加入的元素)移除 CombinedModifier(因为它是链中最后一个元素),并将其赋值给 next。
????????· next 是一个 CombinedModifier 实例,此时就把 inner 和 outer 分别加进 stack 中,这个时候 stack 里面就又有了两个元素:[SizeModifier, PaddingSize],或者说是 [LayoutModifier, LayoutModifier]

????????· 再次循环,这个时候再次开始 removeAt 从末尾取,先取到 PaddingSize,条件判断发现它是一个 Modifier.Element,那就把它加进 result 里面(result 也是一个可变列表)。

????????· 继续循环,再取到 SizeModifier,发现它也是一个 Modifier.Element,那就直接加进 result 里面。

????3. 继续循环,啪~ stack 没东西了,循环结束,直接返回 result 结果:[PaddingModifier, SizeModifier]

这套流程我给你画了张图:

在这里插入图片描述

🖇? 构建双向链表

现在我们来分析第二段核心代码:

// NodeChain.kt
} else if (before.size == 0) {
    attachNeeded = true
    coordinatorSyncNeeded = true
    var i = after.size - 1
    var aggregateChildKindSet = 0
    var node = tail          // 它就是组件自身,只不过封装成了 Modifier.Node
    // 从之前返回的 result 集合里面最后一个元素开始遍历
    while (i >= 0) {
        val next = after[i]  // 最后一个元素( Modifier )
        val child = node     // 组件自身
        node = createAndInsertNodeAsParent(next, child)
        ...
        i--
    }
}

看到这里,你大概就清楚 createAndInsertNodeAsParent 要做什么工作了吧?它传入了两个参数:next 和 child。next 不就是你对组件添加的所有的 Modifier 吗?child 一开始不就是组件自身吗?那么 createAndInsertNodeAsParent 函数是不是要开始对 所有的 Modifier 跟组件自身做一些进一步操作了?

现在我们来仔细看看 createAndInsertNodeAsParent 做了什么:

// NodeChain.kt
private fun createAndInsertNodeAsParent(
    element: Modifier.Element,  // 分解后的 Modifier.Element,例如 PaddingModifier...
    child: Modifier.Node,       // 一开始为组件本身,例如 Box()
): Modifier.Node {
    // 1. 创建一个新的节点
    val node = when (element) {
        // 2. 如果 element 是 ModifierNodeElement 的实例,它会调用 element 上的 create 方法,
        // 这个方法会返回一个新的 Modifier.Node
        is ModifierNodeElement<*> -> element.create().also {
            it.kindSet = calculateNodeKindSetFrom(it)
        }
        else -> BackwardsCompatNode(element)
    }
    ...
    // 3. 插入到树中
    return insertParent(node, child)
}

我们再来看下 insertParent 函数做了什么:

// NodeChain.kt
private fun insertParent(node: Modifier.Node, child: Modifier.Node): Modifier.Node {
    // 获取 child 的父节点
    val theParent = child.parent
    if (theParent != null) {  // 一开始没有父节点,跳过
        theParent.child = node
        node.parent = theParent
    }
    // 将 child 的父节点设置为新节点(node)
    child.parent = node
    // 将新节点(node)的子节点设置为 child
    node.child = child
    // 返回新插入的节点(node)
    return node
}

能理解吗?我也给你画了张图:

在这里插入图片描述

🖇? 同步协调器

现在我们来看看 syncCoordinator 函数:

// NodeChain.kt
private fun syncCoordinators() {
    var coordinator: NodeCoordinator = innerCoordinator
    var node: Modifier.Node? = tail.parent
    while (node != null) {
    	// 遇到 LayoutModifier 的处理
        // 创建一个 LayoutModifierNodeCoordinator,也是用来做测量的
        // 让其他 Modifier 会受到某一个 LayoutModifier 的限制影响
        if (node.isKind(Nodes.Layout) && node is LayoutModifierNode) {
            // 如果节点已有关联的 coordinator,则使用现有的协调器,同时判断此协调器所关联的节点是否变化了,
            // 如果有变化,则调用 onLayoutModifierNodeChanged() 方法
            val next = if (node.coordinator != null) {
                val c = node.coordinator as LayoutModifierNodeCoordinator
                val prevNode = c.layoutModifierNode
                c.layoutModifierNode = node
                if (prevNode !== node) c.onLayoutModifierNodeChanged()
                c
            } else {
                // 如果节点没有关联的 coordinator,则新建一个 LayoutModifierNodeCoordinator,
                // 并通过 updateCoordinator 方法将这个新协调器和节点相关联。
                val c = LayoutModifierNodeCoordinator(layoutNode, node)
                node.updateCoordinator(c)
                c
            }
            // 不论是使用旧的还是新建的 coordinator,通过设置 wrappedBy 和 wrapped 属性,
            // 建立当前 coordinator 与下一个 coordinator 的包装关系,
            // 然后将 coordinator 变量更新为下一个 coordinator
            coordinator.wrappedBy = next
            next.wrapped = coordinator
            coordinator = next
        } else {
            // 如果 node 不是布局修饰符节点,直接调用 updateCoordinator 方法来更新协调器,
            // 将 node 和 NodeCoordinator 挂接关联
            node.updateCoordinator(coordinator)
        }
        // 在每次迭代结束时,将 node 变量更新为当前节点的父节点 node.parent,为下一次循环准备
        node = node.parent
    }
    coordinator.wrappedBy = layoutNode.parent?.innerCoordinator
    outerCoordinator = coordinator    // 调整外层 NodeCoordinator
}

看懂了吗?我又画了一张图:

在这里插入图片描述

简单总结下 updateFrom() 的处理步骤:

  1. 在 Composable 编写的 Modifier 是层层嵌套的,首先需要将 Modifier 集合铺平到一个数组中(与程序编写 Modifier 的顺序相反展开存到数组),方便后续使用这个数组遍历
  2. 如果 NodeChain 还没有组装过双向链表,遍历步骤一铺平的 Modifier 数组组装成双向链表;否则就对双向链表增量更新
  3. 记录双向链表的头节点和尾节点
  4. 将 Modifier 和所属的 NodeCoordinator 挂接关联。

我们可以梳理一下:

1. 不设置 Modifier

   // 比如:Text()
   outerCoordinator = InnerNodeCoordinator

2. 一个 LayoutModifer

   // 比如:Text(Modifier.padding(10.dp))
   outerCoordinator = 
      LayoutModifierNodeCoordinator[
         LayoutModifier                     // PaddingModifier
         +
         InnerNodeCoordinator
      ]

3. 两个 LayoutModifier

   // 比如:Text(Modifier.padding(10.dp).size(30.dp))
   outerCoordinator = 
      LayoutModifierNodeCoordinator[
         LayoutModifier                     // PaddingModifier
         + 
         LayoutModifierNodeCoordinator[
            LayoutModifier                  // SizeModifier
            +
            InnerNodeCoordinator
         ]
      ]

逻辑非常清晰,一层套一层。

我们之前说过,outerCoordinator 对象是做实际测量工作的,如果有 LayoutModifer,那么就会交由 LayoutModifierNodeCoordinator 做综合测量了。

3.3 LayoutModifierNodeCoordinator.measure()

我们看下 LayoutModifierNodeCoordinator 是怎么做测量的:

// LayoutModifierNodeCoordinator.kt
internal class LayoutModifierNodeCoordinator(
    layoutNode: LayoutNode,
    measureNode: LayoutModifierNode,
) : NodeCoordinator(layoutNode) {

    override fun measure(constraints: Constraints): Placeable {
        performingMeasure(constraints) {
            // 1. 核心代码,with 包含了 LayoutModifierNode,提供了一个 LayoutModifierNode 的上下文
            with(layoutModifierNode) {
                // 2. 那么这个 measure 会跳转到哪里?
                measureResult = measure(wrappedNonNull, constraints)
                this@LayoutModifierNodeCoordinator
            }
        }
        onMeasured()
        return this
    }
    
}

3.4 LayoutModifierNode.measure()

measure() 的工作会跳转到哪里,是由 with() 决定的,源码跳转到了 LayoutModifierNode 里面。

// LayoutModifierNode.kt
interface LayoutModifierNode : Remeasurement, DelegatableNode {

    fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult

    ... ...
}

LayoutModifierNode 只是一个接口,所以具体的测量实现在哪?

跟着我的节奏考虑:

?--> 为什么跳转到了 LayoutModifierNode?-- 因为 with() 里面是一个 LayoutModifierNode 的上下文

????????--> LayoutModifierNode 是哪来的?-- 你传进来的

???????????????--> 你从哪传进来的?

还记得一开始修改完报错的全套代码吗:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green)) {
                    Text("Hi Compose",
                        // here,熟悉吗?我们自己写的代码
                        Modifier.layout { measurable, constraints ->
                            val paddingPx = 10.dp.roundToPx()
                            val placeable = measurable.measure(constraints.copy(
                                maxWidth = constraints.maxWidth - paddingPx * 2,
                                maxHeight = constraints.maxHeight - paddingPx * 2
                            ))
                            layout(placeable.width + paddingPx * 2, placeable.height + paddingPx * 2) {
                                placeable.placeRelative(paddingPx, paddingPx)
                            }
                        })
                }

                Modifier.padding()
            }
        }
    }
}

点进 Modifier.layout {} 看源码:

fun Modifier.layout(
    measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) = this.then(
    LayoutModifierImpl(
        measureBlock = measure,
        inspectorInfo = debugInspectorInfo {
            name = "layout"
            properties["measure"] = measure
        }
    )
)

再点击 LayoutModifierImpl 进去看看:

private class LayoutModifierImpl(
    val measureBlock: MeasureScope.(Measurable, Constraints) -> MeasureResult,
    inspectorInfo: InspectorInfo.() -> Unit,
) : LayoutModifier, InspectorValueInfo(inspectorInfo) {
    // 看!!!是不是重写了 MeasureScope.measure 方法 ? 所以具体测量工作就在这里了,我们找到了!
    // 同理,像 Modifier.padding(),你也可以点进去看源码,同样重写了 MeasureScope.measure 方法。
    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ) = measureBlock(measurable, constraints)

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        val otherModifier = other as? LayoutModifierImpl ?: return false
        return measureBlock == otherModifier.measureBlock
    }

    override fun hashCode(): Int {
        return measureBlock.hashCode()
    }

    override fun toString(): String {
        return "LayoutModifierImpl(measureBlock=$measureBlock)"
    }
}

这里 measure 直接调用了 measureBlock(),它是参数传进来的,往回退:

fun Modifier.layout(
    measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) = this.then(
    LayoutModifierImpl(
        measureBlock = measure,
        inspectorInfo = debugInspectorInfo {
            name = "layout"
            properties["measure"] = measure
        }
    )
)

measureBlock 又是 measure,而 measure 是啥?就是你主代码里面写在 Modifier.layout {} 里面的测量和布局逻辑。

文章来源:https://blog.csdn.net/pepsimaxin/article/details/135692045
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。