" 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 包含了两个参数:
- measurable:用于子元素的测量和位置放置;
- 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 也是一个接口,它里面也有 width 和 height,继续修复刚才的报错:
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 对象,同时它还帮我们干了另外两件没做的事:
- 给 alignmentLines 设定了默认值;
- 实现了 placeChildren()。
所以,我们现在只需要补全 layout() 函数剩余的两个参数:width 和 height。
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,它是什么?
- measurable:用于子元素的测量和位置放置的;
- 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 里面必然会存在一个 modifer 属性来处理你所设定的 Modifer.xx,我们来看源码:
3.1 LayoutNode -> modifier
// LayoutNode.kt
internal class LayoutNode(..) {
// 可以理解该 modifier 是 Composable 所有 modifier 的组合
override var modifier: Modifier = Modifier
// 如果有新值变化
set(value) {
...
nodes.updateFrom(value) // 关键代码
...
}
}
3.2 updateFrom()
来看看 updateFrom 的具体工作:
// 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)
// fillVector 会将 Modifier 展开铺平到一个数组,后面的代码就可以用这个数组遍历
val after = m.fillVector(buffer ?: mutableVectorOf())
if (after.size == before.size) {
...
} 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
// 组装双向链表的具体逻辑
node = createAndInsertNodeAsParent(next, child)
...
i--
}
}
...
// 将头节点和尾节点保存到 head 和 tail
trimChain()
// 将 Node 和所属的 NodeCoordinator 挂接关联
if (coordinatorSyncNeeded) {
syncCoordinators()
}
...
}
}
3.3 syncCoordinators()
// NodeChain.kt
internal class NodeChain(val layoutNode: LayoutNode) {
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) {
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 {
val c = LayoutModifierNodeCoordinator(layoutNode, node)
node.updateCoordinator(c)
c
}
coordinator.wrappedBy = next
next.wrapped = coordinator
coordinator = next
} else {
// 将 node 和 NodeCoordinator 挂接关联,其实就是记录在变量
node.updateCoordinator(coordinator)
}
node = node.parent
}
coordinator.wrappedBy = layoutNode.parent?.innerCoordinator
outerCoordinator = coordinator // 调整外层 NodeCoordinator
}
}
简单总结下 updateFrom() 的处理步骤:
- 在 Composable 编写的 Modifier 是层层嵌套的,首先需要将 Modifier 集合铺平到一个数组中(与程序编写 Modifier 的顺序相反展开存到数组),方便后续使用这个数组遍历
- 如果 NodeChain 还没有组装过双向链表,遍历步骤一铺平的 Modifier 数组组装成双向链表;否则就对双向链表增量更新
- 记录双向链表的头节点和尾节点
- 将 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.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 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 {}
里面的测量和布局逻辑。
到这里所有原理都已经讲清楚了,看不懂的话,你就再看一遍!