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

在这里插入图片描述


"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: Intval height: Intval 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: Intget() = TODO("Not yet implemented")override val width: Intget() = 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.heightoverride val height: Intget() = placeable.height// 宽:placeable.widthoverride val width: Intget() = 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: Intget() = TODO("Not yet implemented")override val width: Intget() = 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 = widthoverride val height = heightoverride val alignmentLines = alignmentLinesoverride 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: NodeCoordinatorget() = layoutNode.nodes.outerCoordinatorprivate 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 measurePassDelegateget() = 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: NodeCoordinatorget() = nodes.outerCoordinatorinternal 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 是 innerCoordinatorinternal var outerCoordinator: NodeCoordinator = innerCoordinatorprivate 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 = innerCoordinatorprivate setinternal val tail: Modifier.Node = innerCoordinator.tailinternal var head: Modifier.Node = tailprivate set...
}

注意看:头尾节点是 Modifier.Node 类型!

3.2 updateFrom()

接下来看看 updateFrom 的具体工作,它主要负责 NodeChain 链表的更新。

// NodeChain.kt
internal class NodeChain(val layoutNode: LayoutNode) {// 负责最内层测量的 NodeCoordinatorinternal val innerCoordinator = InnerNodeCoordinator(layoutNode)// 负责外层测量的 NodeCoordinator,初始值是 InnerNodeCoordinatorinternal var outerCoordinator: NodeCoordinator = innerCoordinatorprivate set// 双向链表的尾节点internal val tail: Modifier.Node = innerCoordinator.tail// 双向链表的头节点internal var head: Modifier.Node = tailprivate set...internal fun updateFrom(m: Modifier) {...val before = current ?: MutableVector(capacity = 0)// fillVector 会将 Modifier 展开铺平到一个数组,后面的代码就可以用这个数组遍历val after = m.fillVector(buffer ?: mutableVectorOf())if (after.size == before.size) {...} else if (before.size == 0) {// 第一次组装双向链表// 遍历上面铺平的数组,然后将 Modifier 装进 Node 组装成双向链表attachNeeded = truecoordinatorSyncNeeded = truevar i = after.size - 1var aggregateChildKindSet = 0var node = tailwhile (i >= 0) {val next = after[i]val child = node// 组装双向链表的具体逻辑node = createAndInsertNodeAsParent(next, child)...i--}}...// 将头节点和尾节点保存到 head 和 tailtrimChain()// 将 Node 和所属的 NodeCoordinator 挂接关联if (coordinatorSyncNeeded) {syncCoordinators()}...}
}

3.3 Modifier.fillVector()

我们先来看一下 fillVector 扩展函数做了哪些事:

// NodeChain.kt
private fun Modifier.fillVector(result: MutableVector<Modifier.Element>
): MutableVector<Modifier.Element> {val stack = MutableVector<Modifier>(result.size).also { it.add(this) }while (stack.isNotEmpty()) {when (val next = stack.removeAt(stack.size - 1)) {is CombinedModifier -> {stack.add(next.inner)stack.add(next.outer)}is Modifier.Element -> result.add(next)else -> next.all {result.add(it)true}}}return result
}

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

3.4 syncCoordinators()

// NodeChain.kt
internal class NodeChain(val layoutNode: LayoutNode) {private fun syncCoordinators() {var coordinator: NodeCoordinator = innerCoordinatorvar node: Modifier.Node? = tail.parentwhile (node != null) {// 遇到 LayoutModifier 的处理// 创建一个 LayoutModifierNodeCoordinator,也是用来做测量的// 让其他 Modifier 会受到某一个 LayoutModifier 的限制影响if (node.isKind(Nodes.Layout) && node is LayoutModifierNode) {val next = if (node.coordinator != null) {val c = node.coordinator as LayoutModifierNodeCoordinatorval prevNode = c.layoutModifierNodec.layoutModifierNode = nodeif (prevNode !== node) c.onLayoutModifierNodeChanged()c} else {val c = LayoutModifierNodeCoordinator(layoutNode, node)node.updateCoordinator(c)c}coordinator.wrappedBy = nextnext.wrapped = coordinatorcoordinator = next} else {// 将 node 和 NodeCoordinator 挂接关联,其实就是记录在变量node.updateCoordinator(coordinator)}node = node.parent}coordinator.wrappedBy = layoutNode.parent?.innerCoordinatorouterCoordinator = coordinator    // 调整外层 NodeCoordinator}}

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

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

我们可以梳理一下:

1. 不设置 Modifier// 比如:Text()outerCoordinator = InnerNodeCoordinator2. 一个 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.4 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.5 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 trueval otherModifier = other as? LayoutModifierImpl ?: return falsereturn 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 {} 里面的测量和布局逻辑。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/633241.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Debian】非图形界面Debian10.0.0安装xfce和lxde桌面

一、安装 1. Debian10.0.0安装xfce桌面 sudo apt update sudo apt install xfce4 startxfce4 2. Debian10.0.0安装lxde桌面 sudo apt-get install lxde安装后重启电脑。 二、说明 XFCE、LXDE 和 GNOME 是三个流行的桌面环境&#xff0c;它们都是为类 Unix 操作系统设计…

目标检测--02(Two Stage目标检测算法1)

Two Stage目标检测算法 R-CNN R-CNN有哪些创新点&#xff1f; 使用CNN&#xff08;ConvNet&#xff09;对 region proposals 计算 feature vectors。从经验驱动特征&#xff08;SIFT、HOG&#xff09;到数据驱动特征&#xff08;CNN feature map&#xff09;&#xff0c;提高特…

Git一台电脑 配置多个账号

Git一台电脑 配置多个账号 Git一台电脑 配置多个账号 常用的Git版本管理有 gitee github gitlab codeup &#xff0c;每个都有独立账号&#xff0c;经常需要在一个电脑上向多个代码仓提交后者更新代码&#xff0c;本文以ssh 方式为例配置 1 对应账号 公私钥生成 建议&#…

「sdkman」「nvm」Linux:基于sdkman安装多版本Java;安装maven;基于nvm安装多版本nodejs;安装yarn

1. 基于sdkman 安装多版本Java Linux环境下管理多版本java可以使用sdkman,官网: https://sdkman.io/ 需要注意sdkman 依赖 zip和unzip 命令,记得提前下载再下载sdkman 安装命令 按官网走很简单: curl -s “https://get.sdkman.io” | bash source “$HOME/.sdkman/bin/sdkm…

ARM64汇编01 - 环境搭建

arm官方手册 由于市面上几乎没有arm相关书籍&#xff0c;所以推荐看官方文档。虽然是英文的&#xff0c;看不下去也要硬看&#xff0c;毕竟搞这方面的还是得有啃英文文档/书籍的能力。 另外&#xff0c;再推荐一个翻译网站&#xff1a;https://www.deepl.com/zh/translator …

架设一台NFS服务器,并按照以下要求配置

1、开放/nfs/shared目录&#xff0c;供所有用户查询资料 2、开放/nfs/upload目录&#xff0c;为192.168.xxx.0/24网段主机可以上传目录&#xff0c; 并将所有用户及所属的组映射为nfs-upload,其UID和GID均为210 3、将/home/tom目录仅共享给192.168.xxx.xxx这台主机&#xff0c;…

大模型微调学习记录-基于GLM-130B

0. 前序背景 论文&#xff1a;GLM-130B: AN OPEN BILINGUAL PRE-TRAINED MODEL GLM2的微调教程 目前GLM2-130B优于或相当GPT-3-175B的性能。 选择130B&#xff08;1300亿参数&#xff09;是从硬件性能考虑&#xff0c;可以在单张A100&#xff08;40Gx8&#xff09;上进行推理…

jvm -Djava.library.path 无法打开共享对象文件:

项目代码修改 java -jar -Xms1024m -Xmx1024m -Dloader.path/data/encrypt/lib -Djava.library.path/data/encrypt/libVtExtAPI.so server-1.0.0-SNAPSHOT.jar 重新启动

VScode远程开发

VScode远程开发 在SSH远程连接一文中&#xff0c;我么介绍了如何使用ssh远程连接Jetson nano端&#xff0c;但是也存在诸多不便&#xff0c;比如:编辑文件内容时&#xff0c;需要使用vi编辑器&#xff0c;且在一个终端内&#xff0c;无法同时编辑多个文件。本节将介绍一较为实用…

基于ORB算法的图像匹配

基础理论 2006年Rosten和Drummond提出一种使用决策树学习方法加速的角点检测算法&#xff0c;即FAST算法&#xff0c;该算法认为若某点像素值与其周围某邻域内一定数量的点的像素值相差较大&#xff0c;则该像素可能是角点。 其计算步骤如下&#xff1a; 1&#xff09;基于F…

VMware Workstation Pro虚拟机搭建

下载链接&#xff1a;Download VMware Workstation Pro 点击上方下载&#xff0c;安装过程很简单&#xff0c;我再图片里面说明 等待安装中。。。。。是不是再考虑怎样激活&#xff0c;我都给你想好了&#xff0c;在下面这个链接&#xff0c;点赞收藏拿走不谢。 https://downl…

Jupyter-Notebook无法创建ipynb文件

文章目录 概述排查问题恢复方法参考资料 概述 用户反馈在 Notebook 上无法创建 ipynb 文件&#xff0c;并且会返回以下的错误。 报错的信息是: Unexpected error while saving file: Untitled5.ipynb attempt to write a readonly database 排查问题 这个是一个比较新的问…

物联网孢子捕捉分析仪在农田起到什么作用

TH-BZ03随着科技的飞速发展&#xff0c;物联网技术在农业领域的应用越来越广泛。其中&#xff0c;物联网孢子捕捉分析仪作为一种先进的设备&#xff0c;在农田中发挥着不可或缺的作用。本文将详细介绍物联网孢子捕捉分析仪在农田中的作用。 一、实时监测与预警 物联网孢子捕捉分…

【webrtc】GCC 7: call模块创建的ReceiveSideCongestionController

webrtc 代码学习&#xff08;三十二&#xff09; video RTT 作用笔记 从call模块说起 call模块创建的时候&#xff0c;会创建 src\call\call.h 线程&#xff1a; 统计 const std::unique_ptr<CallStats> call_stats_;SendDelayStats &#xff1a; 发送延迟统计 const…

WebGL中开发VR(虚拟现实)应用

WebGL&#xff08;Web Graphics Library&#xff09;是一种用于在浏览器中渲染交互式3D和2D图形的JavaScript API。要在WebGL中开发VR&#xff08;虚拟现实&#xff09;应用程序&#xff0c;您可以遵循以下一般步骤&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&a…

实验笔记之——基于TUM-RGBD数据集的SplaTAM测试

之前博客对SplaTAM进行了配置&#xff0c;并对其源码进行解读。 学习笔记之——3D Gaussian SLAM&#xff0c;SplaTAM配置&#xff08;Linux&#xff09;与源码解读-CSDN博客SplaTAM全称是《SplaTAM: Splat, Track & Map 3D Gaussians for Dense RGB-D SLAM》&#xff0c;…

在红墙下的冬日幻想:Pygame库实现下雪动画

在红墙下的冬日幻想&#xff1a;借助Pygame库实现下雪动画 寒风轻拂着故宫红墙&#xff0c;我静静地思念着你。这个冬天&#xff0c;借助 Python 的 Pygame 库&#xff0c;我为你呈现一场梦幻般的下雪动画&#xff0c;让雪花在故宫红墙的映衬下在屏幕上翩翩起舞。 准备 首先…

【C++】:STL序列式容器list源码剖析

一、list概述 总的来说&#xff1a;环形双向链表 特点&#xff1a; 底层是使用链表实现的&#xff0c;支持双向顺序访问 在list中任何位置进行插入和删除的速度都很快 不支持随机访问&#xff0c;为了访问一个元素&#xff0c;必须遍历整个容器 与其他容器相比&#xff0c;额外…

【AI预测】破晓未来教育市场:如何精准定位、精选师资并启动高潜力培训项目

在当前全球化和技术快速迭代的背景下&#xff0c;各行业正面临巨大的人才缺口和新的发展机遇。 全球化浪潮&#xff0c;各行业如同搭乘上了一列高速列车&#xff0c;不断深入探索并广泛应用AI技术以提升产业效率、创新服务模式。在智能制造领域&#xff0c;工业4.0时代犹如给…

【leetcode】消失的数字

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家刷题&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 1.暴力求解法2.采用异或的方法&#xff08;同单身狗问题&#xff09;3.先求和再减去数组元素 点击查看…