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

发布时间:2024年01月18日

在这里插入图片描述


" 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() 两个函数处理。

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

2.2 LayoutNode.remeasure()

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

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 来处理。

internal class LayoutNodeLayoutDelegate(
    private val layoutNode: LayoutNode,
    var outerWrapper: LayoutNodeWrapper
) {

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

internal class LayoutNodeLayoutDelegate(
    private val layoutNode: LayoutNode,
    var outerWrapper: LayoutNodeWrapper
) {

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

}

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

2.5 Measurable.measure()

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 的?

internal class LayoutNodeLayoutDelegate(
    private val layoutNode: LayoutNode,
    // 2. 只是一个 LayoutNodeWrapper,所以我们得找到哪里传入了 outerWrapper
    var outerWrapper: LayoutNodeWrapper
) {

	private fun performMeasure(constraints: Constraints) {
       ... ...
       // 1. 这里调用了 measure(),而 outerWrapper 是什么?
       outerWrapper.measure(constraints)
       ... ...

}

继续回退到上一层:

internal class LayoutNodeLayoutDelegate(
    private val layoutNode: LayoutNode,
    var outerWrapper: LayoutNodeWrapper
) {

	inner class MeasurePassDelegate : Measurable, Placeable(), AlignmentLinesOwner {
	
	    // 2. 我们继续回退
		fun remeasure(constraints: Constraints): Boolean {
		    ... ...
		    // 1. 没有地方传入 outWrapper 啊
	        performMeasure(constraints)
	        ... ...
		}
		
    }
}

继续回退到上一层:

internal class LayoutNode(
) {

    // 2. 通过 layoutDelegate 获取 measurePassDelegate,那 layoutDelegate 是什么?
    private val measurePassDelegate
        get() = layoutDelegate.measurePassDelegate
    ... ...
    
    // 4. 而 innerLayoutNodeWrapper 是一个 InnerPlaceable。
    internal val innerLayoutNodeWrapper: LayoutNodeWrapper = InnerPlaceable(this)
    /**
     * 3. LayoutNodeLayoutDelegate(this, innerLayoutNodeWrapper) 看到这行代码你就熟悉了吧?
     *    我们之前一直找的那个 outWrapper 就是 innerLayoutNodeWrapper。
     */
    internal val layoutDelegate = LayoutNodeLayoutDelegate(this, innerLayoutNodeWrapper)
    internal val outerLayoutNodeWrapper: LayoutNodeWrapper
        get() = layoutDelegate.outerWrapper
        
    internal fun remeasure(
        constraints: Constraints? = layoutDelegate.lastConstraints
    ): Boolean {
        return if (constraints != null) {
            ... ...
            // 1. measurePassDelegate 是什么?
            measurePassDelegate.remeasure(constraints)
        } else {
            false
        }
    }

}

所以,到底是哪个实现类处理了 measure() 方法,就不用我说了吧?

在这里插入图片描述

2.6 InnerPlaceable.measure()

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

internal class InnerPlaceable(
    layoutNode: LayoutNode
) : LayoutNodeWrapper(layoutNode), Density by layoutNode.measureScope {

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

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

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

所以,Do you understand?

在这里插入图片描述


三、LayoutModifer 的工作原理


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

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

3.1 LayoutNode -> modifier

internal class LayoutNode(
    private val isVirtual: Boolean = false
) : Remeasurement, OwnerScope, LayoutInfo, ComposeUiNode,
    Owner.OnLayoutCompletedListener {
 
    override var modifier: Modifier = Modifier
        // 如果有新值变化
        set(value) {
			... ...

            /** foldOut: 从右往左遍历
             *  比如:
             *      Modifier.padding().background(): 先 backgroud() -> 后 padding()
             */
            val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap ->
                if (mod is RemeasurementModifier) {
                    mod.onRemeasurementAvailable(this)
                }

                toWrap.entities.addBeforeLayoutModifier(toWrap, mod)

                if (mod is OnGloballyPositionedModifier) {
                    getOrCreateOnPositionedCallbacks() += toWrap to mod
                }

                // 1. 这里就是 LayoutModifier 的处理逻辑
                val wrapper = if (mod is LayoutModifier) {
                    /**
                     * 2. 如果是 LayoutModifier,就创建一个 ModifiedLayoutNode,
                     * 它有两个参数:toWrap, mod
                     *     ? mod: 就是一步步遍历到的 Modifier,在我们这篇文章例子里面它就是 LayoutModifier
                     *     ? toWrap: 初始值就是 innerLayoutNodeWrapper -> InnerPlaceable
                     */
                    (reuseLayoutNodeWrapper(toWrap, mod)
                        ?: ModifiedLayoutNode(toWrap, mod)).apply {
                        onInitialize()
                        updateLookaheadScope(mLookaheadScope)
                    }
                } else {
                    toWrap
                }
                wrapper.entities.addAfterLayoutModifier(wrapper, mod)
                // 3. 把处理完的 wrapper 赋值给 outerWrapper
                wrapper
            }
            
            setModifierLocals(value)

            outerWrapper.wrappedBy = parent?.innerLayoutNodeWrapper
            // 4. 这里又把 layoutDelegate 的 outerWrapper 给换了
            layoutDelegate.outerWrapper = outerWrapper
            
            ... ...
        }
}

我们可以梳理一下:

1. 不设置 Modifier:
   outWrapper = innerLayoutNodeWrapper -> 测量 Compose 函数,比如 Text()

2. 一个 LayoutModifer:
   outerWrapper = 
      ModifiedLayoutNode[
         LayoutModifier
         +
         innerLayoutNodeWrapper -> 测量 Compose 函数,比如 Text()
      ]

3. 两个 LayoutModifier:
   outerWrapper = 
      ModifiedLayoutNode[
         LayoutModifier
         + 
         ModifiedLayoutNode[
            LayoutModifier
            +
            innerLayoutNodeWrapper -> 测量 Compose 函数,比如 Text()
         ]
      ]

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

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

3.2 ModifiedLayoutNode.measure()

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

internal class ModifiedLayoutNode(
    override var wrapped: LayoutNodeWrapper,
    var modifier: LayoutModifier
) : LayoutNodeWrapper(wrapped.layoutNode) {

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

}

3.3 LayoutModifier.measure()

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

interface LayoutModifier : Modifier.Element {

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

    ... ...
}

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

跟着我的节奏考虑:

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

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

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

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

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/135672518
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。