阅读 1813
Jetpack Compose 测量流程源码分析

Jetpack Compose 测量流程源码分析

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

本文你将学到什么

通过一段代码场景,以Layout 函数为入口分析源码,解答一些心中所惑,如通过 Modifier 设置大小是如果起作用的?MeasurePolicy 接口的 measure 方法是怎么调用的?布局中的测量流程是什么样的?控件是怎么确认大小的?

回顾

JetPack Compose 手写一个 Row 布局 | 自定义布局 一文中我们已经了解了如何自定义 Layout,使用 Layout 函数即可。

@Composable inline fun Layout(
    content: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {}
   
复制代码

我们可以通过参数 modifier 给布局指定大小,在 measurePolicy 中对 children 进行测量和布置,布局的 children 写在 content 函数中。使用起来很方便嘛😁 ,但是好奇心让我想知道这个Layout里面做了些什么?🤔 这里面的源码可能很复杂,但还是想尝试着看一看,不试一试怎么知道呢。 🚀

为了方便探究和调试代码,本文以下面代码为场景进行分析。

@Composable
private fun ParentLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
  //布局的测量策略
    val measurePolicy = MeasurePolicy { measurables, constraints ->
        //1.测量 children
        val placeables = measurables.map { child ->
            child.measure(constraints)
        }

        var xPosition = 0
        //2.放置 children
        layout(constraints.minWidth, constraints.minHeight) {
            placeables.forEach { placeable ->
                placeable.placeRelative(xPosition, 0)
                xPosition += placeable.width
            }
        }
    }
    //代码分析入口
    Layout(content = content, modifier = modifier, measurePolicy = measurePolicy)
}

@Composable
private fun ChildLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
    //...代码和ParentLayout类似
}

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
           ParentLayout(
                Modifier
                    .size(100.dp)
                    .padding(10.dp)
                    .background(Color.Blue)
            ) {
                ChildLayout {
                    Box {}
                }
                ChildLayout {}
            }
        }
    }
}

复制代码

本次探索希望能回答下面几个问题

  1. ParentLayout 中 通过 modifier 设置大小是如何起到作用的 ?
  2. MeasurePolicy 接口的 measure 方法是怎么调用的?他的参数值是怎么来的呢?
  3. 布局中的测量流程是什么样的?

下面就带着上面这些问题,在看源码的过程中尝试去解释这些问题。

本文源码对应版本 compose_version = '1.0.0-rc01'

跟踪 Modifier & MeasurePolicy

为了方便跟踪代码,我来给代码设置点跟踪器 (别搞丢了)😜 下面的代码中 modifier 参数达到的位置我会用📍 标记, measurePolicy 到达的位置用 📌 标记

Layout.kt → Layout 函数源码


@Composable inline fun Layout(
    content: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {
    val density = LocalDensity.current
    val layoutDirection = LocalLayoutDirection.current
    ReusableComposeNode<ComposeUiNode, Applier<Any>>(
        factory = ComposeUiNode.Constructor,
        update = {
            set(measurePolicy, ComposeUiNode.SetMeasurePolicy) // 👈  📌 measurePolicy 在这
            set(density, ComposeUiNode.SetDensity)
            set(layoutDirection, ComposeUiNode.SetLayoutDirection)
        },
        skippableUpdate = materializerOf(modifier), // 👈  📍 modifier 在这
        content = content 
    )
}
复制代码

从上面源码可以看出,Layout 函数体中没有做什么处理,核心内容就是调用 ReusableComposeNode 函数。

@Composable 注解的函数建议首字母大写已区分普通函数,看代码的时候总觉的 ReusableComposeNode 是个类,点进去发现它是个 Composable 函数 😂 。

Composables.kt → ReusableComposeNode 函数

inline fun <T, reified E : Applier<*>> ReusableComposeNode(
    noinline factory: () -> T,
    update: @DisallowComposableCalls Updater<T>.() -> Unit,
    noinline skippableUpdate: @Composable SkippableUpdater<T>.() -> Unit,
    content: @Composable () -> Unit
) {
    if (currentComposer.applier !is E) invalidApplier()
    currentComposer.startNode()
    if (currentComposer.inserting) {
        currentComposer.createNode(factory)
    } else {
        currentComposer.useNode()
    }
    
    //执行update函数
    Updater<T>(currentComposer).update() // 👈  📌 measurePolicy 在这
    
     //执行skippableUpdate函数
    SkippableUpdater<T>(currentComposer).skippableUpdate()  // 👈  📍 modifier 在这函数中
    
    currentComposer.startReplaceableGroup(0x7ab4aae9)
    content()
    currentComposer.endReplaceableGroup()
    currentComposer.endNode()
}
复制代码

先只看和 modifier 有关的,即 SkippableUpdater<T>(currentComposer).skippableUpdate() 这句话,其他的先不看。

这里函数的参数都是函数参数类型,如果 Kotlin 不太熟悉的,可能看着比较晕 😵 ,下面为了方便分析一下,就把代码给它摊平了。

这里的 "代码给它摊平了" 是指去掉函数回调和一层层的调用,直接写到一起,避免函数跳来跳去,方便解释。下文中提到的摊平都是这个意思。

下面我就试着把它摊平看看。

//源代码
SkippableUpdater<T>(currentComposer).skippableUpdate() 

//0️⃣ 根据 ComposeNode 传入参数知
skippableUpdate=materializerOf(modifier) // 👈  📍 modifier 

//1️⃣ materializerOf 函数返回值就是函数类型 SkippableUpdater<ComposeUiNode>.() -> Unit
internal fun materializerOf(
    modifier: Modifier 
): @Composable SkippableUpdater<ComposeUiNode>.() -> Unit = {
     //  📍 这里只是对 modifier链中存在的 ComposedModifier 进行处理一下,返回值还是 Modifier
    val materialized = currentComposer.materialize(modifier) 
    update { set(materialized, ComposeUiNode.SetModifier)  } // 👈  📍 modifier
}

//结合代码 0️⃣ 和代码 1️⃣ 可知 SkippableUpdater<T>(currentComposer).skippableUpdate() 
//<=> 等价于代码如下
val skippableUpdater=SkippableUpdater<ComposeUiNode>(currentComposer)
val materialized = currentComposer.materialize(modifier)
skippableUpdater.update { set(materialized, ComposeUiNode.SetModifier)  }
复制代码

好像还差点还有个update 还是没有摊平

inline class SkippableUpdater<T> constructor(
    @PublishedApi internal val composer: Composer
) {
    inline fun update(block: Updater<T>.() -> Unit) {
        composer.startReplaceableGroup(0x1e65194f)
        Updater<T>(composer).block()
        composer.endReplaceableGroup()
    }
}

//结合SkippableUpdater 的update 函数,skippableUpdater.update { set(materialized, ComposeUiNode.SetModifier) }
// <=>等价于👇
composer.startReplaceableGroup(0x1e65194f)
// 2️⃣ 📍 modifier 最终传给了这个 set 方法
Updater<ComposeUiNode>(currentComposer).set(materialized, ComposeUiNode.SetModifier)
composer.endReplaceableGroup()
复制代码

代码 2️⃣ 这里是调用了一个 set 方法,直接看有点晕,调来调用去,分析起来太多了,不能跑偏了,直接说重点。

 companion object {
        val Constructor: () -> ComposeUiNode = LayoutNode.Constructor
     	//ComposeUiNode.SetModifier
        val SetModifier: ComposeUiNode.(Modifier) -> Unit = { this.modifier = it }
    }
// ComposeUiNode.SetModifier 也是个函数类型,调用 set(materialized, ComposeUiNode.SetModifier) 
//最终会触发SetModifier 函数的执行也就是
	this.modifier=materialized //📍 modifier
 //	👆 this是LayoutNode 对象 是通过触发 ComposeUiNode.Constructor创建的


复制代码

关于从 set(materialized, ComposeUiNode.SetModifier) 是如何到触发 SetModifier 函数的,这里我就分析了,可以通过 debug 很容易验证这一结论。如果你真的想去分析如何执行的话,分析之前建议先看一下 深入详解 Jetpack Compose | 实现原理 这篇文章。(友情提醒,如何真要分析这段别陷进去了,别忘记我们看源码的目的。)

通过上面的分析,我们追踪的 modifier 被赋值给了 LayoutNode 成员的 modifier ,这种是个赋值语句,在 kotlin 相当于调用的成员变量的set 方法 LayoutNode.kt

  override var modifier: Modifier = Modifier
        set(value) {
            // …… code
            field = value
            // …… code

        
            // 创建新的 LayoutNodeWrappers 链
            // foldOut 相当于遍历 modifier
            val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod /*📍 modifier*/ , toWrap ->
                var wrapper = toWrap
                if (mod is OnGloballyPositionedModifier) {
                    onPositionedCallbacks += mod
                }
                if (mod is RemeasurementModifier) {
                    mod.onRemeasurementAvailable(this)
                }

                val delegate = reuseLayoutNodeWrapper(mod, toWrap)
                if (delegate != null) {
                    wrapper = delegate
                } else {
                      // …… 省略了一些 Modifier判断 
                      if (mod is KeyInputModifier) {
                        wrapper = ModifiedKeyInputNode(wrapper, mod).assignChained(toWrap)
                    }
                    if (mod is PointerInputModifier) {
                        wrapper = PointerInputDelegatingWrapper(wrapper, mod).assignChained(toWrap)
                    }
                    if (mod is NestedScrollModifier) {
                        wrapper = NestedScrollDelegatingWrapper(wrapper, mod).assignChained(toWrap)
                    }
                    // 布局相关的 Modifier
                    if (mod is LayoutModifier) {
                        wrapper = ModifiedLayoutNode(wrapper, mod).assignChained(toWrap)
                    }
                    if (mod is ParentDataModifier) {
                        wrapper = ModifiedParentDataNode(wrapper, mod).assignChained(toWrap)
                    }
                   
                }
                wrapper
            }

            outerWrapper.wrappedBy = parent?.innerLayoutNodeWrapper
            // 代码 0️⃣
            outerMeasurablePlaceable.outerWrapper = outerWrapper // 👈  📍 modifier

            ……
        }
复制代码

👆 代码片段-1

上述代码主要是将Modifier 链转换LayoutNodeWrapper 链的过程,通过Modifier 的 foldOut 函数 遍历Modifier 链上的所有元素,并根据不同的Modifier 创建不同的 LayoutNodeWrapper。关于Modifier 的foldOut 函数的作用不懂的可以看我之前写的 Modifier源码,Kotlin高阶函数用的真6 这篇文章。

在上面的代码中根据 Modifier 类型创建不同的 LayoutNodeWrapper,这些不同的 Modifier 都是 Modifier.Element 的直接实现类或接口,如 KeyInputModifier、PointerInputModifier、LayoutModifier 等。上面代码都是 if 判断,没有else,也就是说如果 Modifier 不在这些类别范围内就没法创建对应的LayoutNodeWrapper,也就相等于我们设置的 Modifier 没有用。所以我自定义Modifer 一定要在这个类型范围内,否则是没有用的。在JetPack Compose 内置的Modifier.Element 子类或接口如下。(Tips. Android studio 查看类的继承关系 菜单栏Navgate-> Type Hierarchy ; 快捷键 Ctrl+H )

image.png

换个思路继续跟踪

上面分析到那里路好像断了, 没法继续了。思考一下这里只是分析了Layout 函数执行时,只是初始化的准备工作。它的大小和位置如果确认等操作这里似乎没有执行。我们刚才是把 ParentLayout 当做父容器来看待的,父容器一般是管理自己的 children 的大小和位置,换一种思路,ParentLayout 出来做父容器,它也可以作为 child 呀,如下面代码情况。

setContent {
    ParentLayout{
        Box() {}
        // 👇 可以看做是 上面 ParentLayout 的 child,也可以看做是下面 ChildLayout 的父容器
        ParentLayout(
            Modifier
                .size(100.dp)
                .padding(10.dp)
                .background(Color.Blue)
        ) {
            ChildLayout(Modifier.size(100.dp)) {}
        }       
    }
}
复制代码

下面就从 ParentLayout 布局作为 child 的时候来分析一下,如果作为 child 那么分析入口就应该它从的父容器 MeasurePolicy 的 measure 函数开始分析了。

    val measurePolicy = MeasurePolicy { measurables, constraints ->
        val placeables = measurables.map { child ->
            //代码 0️⃣    
            child.measure(constraints)
        }
       ……
    }
复制代码

代码 0️⃣ 进行调用 child 的测量方法,从函数参数来看,只知道 child 是个 Measurable 的类型,但 Measurable 是个接口,我们需要知道 child 具体是 Measurable 那个实现类,我们才好分析 measure 函数的逻辑 image.png 👆 图片-0

通过debug 的方式,可以看出 child 是 LayoutNode 对象(为什么是LayoutNode 下面分析就知道了),那么就去看看 LayoutNode 的measure函数。

LayoutNode.kt → measure 函数

 override fun measure(constraints: Constraints) =
        outerMeasurablePlaceable.measure(constraints)
复制代码

LayoutNode 的 measure 调用了 outerMeasurablePlaceable 的 measure 函数,这个 outerMeasurablePlaceable **代码片段-1 代码 0️⃣ **也出现了outerMeasurablePlaceable.outerWrapper = outerWrapper // 👈  📍 modifier 而且这个outerMeasurablePlaceable 的属性 outerWrapper 就包含 modifier 信息。我们又找到了 modifier 的藏身之处,好像又找到些线索。我们继续跟踪代码吧。

LayoutNodeWrapper 链中的测量流程分析⛓

OuterMeasurablePlaceable.kt

 override fun measure(constraints: Constraints): Placeable {
        ……
        remeasure(constraints)
        return this
    }
 
     fun remeasure(constraints: Constraints): Boolean {
        val owner = layoutNode.requireOwner()
         ……
        if (layoutNode.layoutState == LayoutState.NeedsRemeasure ||
            measurementConstraints != constraints
        ) {
            measuredOnce = true
            layoutNode.layoutState = LayoutState.Measuring
            measurementConstraints = constraints
            val outerWrapperPreviousMeasuredSize = outerWrapper.size
            owner.snapshotObserver.observeMeasureSnapshotReads(layoutNode) {
                outerWrapper.measure(constraints)//  0️⃣ 👈  📍 modifier
            }
            layoutNode.layoutState = LayoutState.NeedsRelayout
           ……
            return sizeChanged
        }
        return false
    }

复制代码

👆 代码片段-2 代码  0️⃣ 处 我们看到包含 modifier 信息的 outerWrapper 调用了 它的 measure 方法。outerWrapper 是 LayoutNodeWrapper 类型的,它就是在代码片段1 处根据不同 Modifer 创建的 LayoutNodeWrapper 链。我们给 ParentLayout 的 Modifer 设置为 Modifier.size(100.dp).padding(10.dp).background(Color.Blue) 。那么对应的LayoutNodeWrapper 链如下图所示

Jetpack compose 测量流程-LayoutNodeWrapper链 (3).png

👆 图-1

由 图-1 知代码片段-2 处的代码outerWrapper 为 ModifiedLayoutNode 类型。 ModifiedLayoutNode

internal class ModifiedLayoutNode(
    wrapped: LayoutNodeWrapper,
    modifier: LayoutModifier
) : DelegatingLayoutNodeWrapper<LayoutModifier>(wrapped, modifier) {

    override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
        with(modifier) {   👈  📍 modifier
            measureResult = measureScope.measure(wrapped, constraints)
            this@ModifiedLayoutNode
        }
    }
    
    protected inline fun performingMeasure(constraints: Constraints, block: () -> Placeable
    ): Placeable {
        measurementConstraints = constraints
        val result = block()
        layer?.resize(measuredSize)
        return result
    }
    
 ……   
}
复制代码

由 图-1 知代码此时的 modifier 为 SizeModifier 类型 LayoutModifier.kt

interface LayoutModifier : Modifier.Element {
	fun MeasureScope.measure(
        measurable: Measurable,/*下一个 LayoutNodeWrapper 节点*/
        constraints: Constraints/* 来着父容器或者来着上一个节点的约束 */
    ): MeasureResult
}
复制代码

SizeModifier.kt

private class SizeModifier(
    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,
    inspectorInfo: InspectorInfo.() -> Unit
) : LayoutModifier, InspectorValueInfo(inspectorInfo) {
    private val Density.targetConstraints: Constraints
        get() {/*更加我们指定的大小生成对应的约束*/}

    override fun MeasureScope.measure(
        measurable: Measurable,/*下一个LayoutNodeWrapper*/
        constraints: Constraints/* 来着父容器或者来着上一个节点的约束 */
    ): MeasureResult {
        val wrappedConstraints = targetConstraints.let { targetConstraints ->
            if (enforceIncoming) {//当我们给控件指定大小时,这个值就为true
            	//结合父容器或者上一个节点的约束 和我们指定约束进行结合生成一个新的约束
                constraints.constrain(targetConstraints)
            } else {
                ……
            }
        }
        //代码 0️⃣ 进行下一个 LayoutNodeWrapper 节点测量
        val placeable = measurable.measure(wrappedConstraints)
        //所有节点测量完,开始放置
        return layout(placeable.width, placeable.height) {
            placeable.placeRelative(0, 0)
        }
    }
复制代码

代码 0️⃣ 继续进行下一个 LayoutNodeWrapper 节点的测量,一直到最后 InnerPlaceable 节点。

 class LayoutNode{
     internal val innerLayoutNodeWrapper: LayoutNodeWrapper = InnerPlaceable(this)
     private val outerMeasurablePlaceable = OuterMeasurablePlaceable(this, innerLayoutNodeWrapper)
     ……
    internal val children: List<LayoutNode> get() = _children.asMutableList()
 }
复制代码

InnerPlaceable

class InnerPlaceable{   
    override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
        val measureResult = with(layoutNode.measurePolicy) {
            // 这里就是Layout 的MeasurePolicy 的measure 执行的地方了
            layoutNode.measureScope.measure(layoutNode.children, constraints)
        }
        layoutNode.handleMeasureResult(measureResult)
        return this
    }
}
复制代码

这里就是 Layout 的 MeasurePolicy 的measure 执行的地方了,然后children 继续执行上述流程了如下图所示,这样“MeasurePolicy 接口的 measure 方法是怎么调用的?他的参数值是怎么来的呢?”这个问题也就解答了。 image.png

分析解答问题

通过上面的分析,我们大致可以回答“布局中的测量流程是什么样的?” 这个问题了。**

1 准备阶段:

child 在父容器在声明时,也就是调用了 Layout 函数,进行初始化准备操作,记录这个child 测测量策略,这个child 的children 等,根据设置的 Modifier 链创建对应的 LayoutNodeWrapper 链。

2 测量阶段

child 在父容器的测量策略 MeasurePolicy 的 measure 函数中执行 child 的 measure 函数。接着按照准备好的 LayoutNodeWrapper 链一步步的执行各个节点的 measure 函数,最终走到 InnerPlaceable 的 measure 函数,在这个又会继续它的 children 进行测量,此时它的 children 就会和它一样进行执行上述流程,一直到所有children 测量完成。 用下面这张图总结一下上述流程。

Jetpack compose 测量流程-测量流程 (1).png

👆 图-2 测量流程图

还有一个最后一个问题 ParentLayout 中 通过 modifier 设置大小是如何起到作用的 ? 答:我们通过 Modifer.size() 函数 构建了一个SizeModifer 对象

fun Modifier.size(size: Dp) = this.then(
    SizeModifier(
        minWidth = size,maxWidth = size,
        minHeight = size, maxHeight = size,
        enforceIncoming = true,...
    )
)
复制代码

通过上面的分析我们知道,在测量流程中SizeModifer 的measure 函数会触发 image.png

SizeModifer部分源码

private class SizeModifier(
    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,
    inspectorInfo: InspectorInfo.() -> Unit
) : LayoutModifier, InspectorValueInfo(inspectorInfo) {
    // 0️⃣ 根据我们设置的大小生成一个约束对象
    private val Density.targetConstraints: Constraints
    get() {
        //控制 maxWidth值范围
        val maxWidth = if (maxWidth != Dp.Unspecified) {
            maxWidth.coerceAtLeast(0.dp).roundToPx()
        } else {
            Constraints.Infinity
        }
        
        //maxHeight、minWidth、minHeight 也类似处理一下值的范围,防止我们瞎捣乱
		...code...
        
        return Constraints(
            minWidth = minWidth,
            minHeight = minHeight,
            maxWidth = maxWidth,
            maxHeight = maxHeight
        )
    }

    override fun MeasureScope.measure(
        measurable: Measurable,//LayoutNodeWrapper链上的下一个节点的
        constraints: Constraints//父容器或者是LayoutNodeWrapper链上的上一个节点的constraints
    ): MeasureResult {
        val wrappedConstraints = targetConstraints.let { targetConstraints ->
                if (enforceIncoming) {
                    //1️⃣  和我们设置的大小继续比较计算,得出一个新的Constraints对象
                    // constrain 函数作用是,在constraints范围内,得到一个尽可能满足targetConstraints的大小范围的约束
                    constraints.constrain(targetConstraints)
                } else {
					//暂不讨论
                }
                //2️⃣  使用新的约束继续下一个测量
            val placeable = measurable.measure(wrappedConstraints)
            
            return layout(placeable.width, placeable.height) {
                placeable.placeRelative(0, 0)
            }
        }
    }
    
    ...
}
复制代码

我们通过Modifier.size 函数设置的大小信息最终变成SizeModifier 一个成员变量 targetConstraints,它是一个Constraints 类型,主要描述宽度的最小值和最大值(minWidth、maxWidth) 以及高度的最小值最大值(minHeight、maxHeight)。 在 measure 函数中,在代码 1️⃣ 处把传入的 constraints 和我们传入的大小进行大小比较得到一个合适的 Constraints ,然后用新的 Constraints 进行下一个次测量。我们设置的大小就是在这里起到作用的,影响着接下来的测量约束参数。

补充点-布局宽高计算流程

上面我们把measure 测量流程走了一遍,测量的目的就是为了布局的宽高计算,那这些计算的过程是在哪里执行的呢?我们通过 Jetpack Compose 写界面,我们的写每一个组件都会转换成对应的节点 LayoutNoe,在Layout 函数中就有一个创建LayoutNode的过程。我们想分析局的最终宽高,就看 LayoutNode 的宽高怎么来的就行了。

class LayoutNode{
    private val outerMeasurablePlaceable = OuterMeasurablePlaceable(this, innerLayoutNodeWrapper)

    override val width: Int get() = outerMeasurablePlaceable.width

    override val height: Int get() = outerMeasurablePlaceable.height
}
复制代码

看LayoutNode 源码可知,LayoutNode 的宽高就是 OuterMeasurablePlaceable 的宽高,我们找到 OuterMeasurablePlaceable 的宽高确认就可以了,上面我已经把测量流程大致分析了一下,结合 图-2 测量流程图 再把流程走一下就可以找到答案了,这里我就不在一段一段代码的去分析了,我把 OuterMeasurablePlaceable 的宽高计算代码流程绘制成下面图片了,有兴趣的可以结合图片去看源码分析一遍。

Jetpack compose 测量流程-宽高确认流程.png

👆 图-3 节点宽高计算代码流程图

大致流程是,在父容器的MeasurePolicy 进行 child 的测量->进入OuterMeasurablePlaceable的测量函数,在这里先根据函数传入的Constraints约束计算一下宽高,然后沿着LayoutNodeWrapper 链去测量,链的每一个节点根据上一个节点传来的约束进行一次宽高计算,一直到最后的InnerPlaceable的测量,它同样是根据传入的约束计算宽高,然后会测量 children,测量完所以的 chlidren之后得到一个测量结果,根据这个测量结果,InnerPlaceable 再一次进行计算宽高。然后把最终的宽高信息返回给上一个节点,上一个节点根据返回的测量结果信息重新计算宽高,沿着 LayoutNodeWrapper 链反向的返回测量结果,每个节点重新计算后再把结果返回给上一级,一直到 OuterMeasurablePlaceable,它再拿到测量结果重新计算宽高 OuterMeasurablePlaceable 的宽高就是 LayoutNode 的宽高,整个宽高确认的流程大致就是这样了。

Jetpack compose 测量流程-宽高简易流程图.png

👆 节点宽高简易流程图

每一个节点都会根据Constraints 约束计算记一次宽高,同时也把这个约束条件记录了下来,然后根据测量的结果再一次计算宽高,仔细看计算宽高的逻辑你就会发现,大致的原因是这样的,先是按照约束的最小值计算宽高,比如约束是 Constraints(minWidth = 80, maxWidth = 120, minHeight = 90, maxHeight = 150) 那么此时宽高值分别是80,90。经过测量返回宽高结果是100,120,拿这个值和原来记录的约束进行比较,发现宽高的值都在对应的约束范围内,那最终的宽高值就以测量结果的宽高值为准,如果测量结果不在约束范围内,那宽高就取约束条件中对应的最小值或最大值。记录约束条件的目的就是防止测量结果超过此限制。

总结回顾

本文通过追踪Layout 源码,通过分析解答了一些问题,下面简单再回顾一下。

1.布局中通过 modifier 设置大小是如何起到作用的 ?

答:是在 ModifiedLayoutNode 的测量函数中调用 LayoutModifier 的测量函数中起到作用的,比如 SizeModifer 的 measure 函数。详细分析见上文。

2.MeasurePolicy 接口的 measure 方法是怎么调用的?

答:是在 InnerPlaceable 的measure 函数中调用的。详细分析见上文。

3.布局中的测量流程是什么样的及宽高的确认?

答:见图-2 测量流程图和图-3 节点宽高计算代码流程图。详细分析见上文。

文章分类
Android