"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 的文章,这些都是必备基础。
先来看一个最简单的代码示例:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
Box(Modifier.background(Color.Green)) {
Text("Hi Compose")
}
}
}
}
}
这段代码很简单,效果图如下:
接下来基于这个 Demo,我们会慢慢引入本篇文章的主角。
在 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 包含了两个参数:
我们定位到 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() 对组件修饰,都得像上面这样写一堆代码,那还不得疯?
其实在实际开发中我们并不会这么写,而是使用 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 对象,同时它还帮我们干了另外两件没做的事:
所以,我们现在只需要补全 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,它是什么?
前面的例子并没有对 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())
}
}
}
... ...
}
前面所有的示例代码,不论是使用 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 原理之前,有一个核心知识点是必须要提前了解的。
这段代码我们再熟悉不过了:
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() // 测量
}
我们先来分析 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
}
}
}
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)。
// 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),它是做实际测量工作的。
// 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
}
}
}
我们再来看看 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() 方法,你现在清楚了吧?再来看下刚刚的截图:
所以接下来我们就看看 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 是如何影响组件的测量和布局了。
就像我们前面说的那样,所有组件最终都会被转换为一个 LayoutNode,这个 LayoutNode 包含了所有的测量数据,那同样它也会包含你对组件设定的 Modifier,所以最终经过一些列转换,也会传到 LayoutNode 里面,那 LayoutNode 里面必然会存在一个 modifier 属性来处理你所设定的 Modifer.xx,我们来看源码内部是怎么处理的。
在 LayoutNode 中确实也有一个 modifier 属性来处理所有外部传入的 Modifier,代码如下:
// LayoutNode.kt
internal class LayoutNode(..) {
// 可以理解该 modifier 是 Composable 所有 modifier 的组合
override var modifier: Modifier = Modifier
// 如果有新值变化
set(value) {
...
nodes.updateFrom(value) // 关键代码
...
}
}
这里的 nodes 应该不陌生了吧?前一节我们已经知道了它是 NodeChain 对象。
internal val nodes = NodeChain(this)
但是 NodeChain 是什么?其实它就是一个链表,而且是个双向链表。
// NodeChain.kt
internal class NodeChain(val layoutNode: LayoutNode) {
internal val innerCoordinator = InnerNodeCoordinator(layoutNode)
internal var outerCoordinator: NodeCoordinator = innerCoordinator
private set
internal val tail: Modifier.Node = innerCoordinator.tail
internal var head: Modifier.Node = tail
private set
...
}
头、尾节点都是 Modifier.Node 类型!其中的 NodeCoordinator 是用来辅助 Node 节点处理测量和布局的,其中包含 measure 和 placeAt 的函数逻辑。NodeChain链表上的每一个 Node 都会对应的绑定一个 NodeCoordinator 对象来辅助处理。
接下来看看 updateFrom 的具体工作,它主要负责 NodeChain 链表的更新,每当有 Modifier 对象被设置到 LayoutNode 上面,都会调用 updateFrom 函数进行更新对应的 NodeChain。
// NodeChain.kt
internal class NodeChain(val layoutNode: LayoutNode) {
// 负责最内层测量的 NodeCoordinator
internal val innerCoordinator = InnerNodeCoordinator(layoutNode)
// 负责外层测量的 NodeCoordinator,初始值是 InnerNodeCoordinator
internal var outerCoordinator: NodeCoordinator = innerCoordinator
private set
// 双向链表的尾节点
internal val tail: Modifier.Node = innerCoordinator.tail
// 双向链表的头节点
internal var head: Modifier.Node = tail
private set
...
internal fun updateFrom(m: Modifier) {
...
val before = current ?: MutableVector(capacity = 0)
// 核心代码 1: fillVector 会将 Modifier 展开铺平到一个数组,后面的代码就可以用这个数组遍历
val after = m.fillVector(buffer ?: mutableVectorOf())
if (after.size == before.size) {
... // 这段代码不用看,只要加了 Modifier.xx 默认不会走到这边
} else if (before.size == 0) {
// 第一次组装双向链表
// 遍历上面铺平的数组,然后将 Modifier 装进 Node 组装成双向链表
attachNeeded = true
coordinatorSyncNeeded = true
var i = after.size - 1
var aggregateChildKindSet = 0
var node = tail
while (i >= 0) {
val next = after[i]
val child = node
// 核心代码 2: 组装双向链表的具体逻辑
node = createAndInsertNodeAsParent(next, child)
...
i--
}
}
...
// 将头节点和尾节点保存到 head 和 tail
trimChain()
// 将 Node 和所属的 NodeCoordinator 挂接关联
if (coordinatorSyncNeeded) {
syncCoordinators()
}
...
}
}
接下来跟着我深入分析每一行核心代码!希望你能沉下心看下去,肯定能看懂!
先来看第一个核心代码:
val after = m.fillVector(buffer ?: mutableVectorOf())
m 就是你的 Modifier 链,这里的 buffer 默认为 null,mutableVectorOf() 其实跟 buffer 一样都是一个可变列表,只是容量不同而已,你只要知道它就是一个列表,用来装分解后的单个 Modifier 用的就行了。
接着我们开始分析 fillVector 函数,仔细看注释!
// NodeChain.kt
private fun Modifier.fillVector(
result: MutableVector<Modifier.Element>
): MutableVector<Modifier.Element> {
// 1. 创建一个元素为 Modifier 的可变列表,并在初始化后添加传进来的 Modifier,
// 从 stack 名字就可以看出来,这里相当于创建了一个栈
val stack = MutableVector<Modifier>(result.size).also { it.add(this) }
// 2. 只要栈不为空,一直循环
while (stack.isNotEmpty()) {
// 3. 从栈中移除并获取最后一个元素,存到 next 变量中
when (val next = stack.removeAt(stack.size - 1)) {
// 4. 如果 next 是 CombinedModifier 类型
is CombinedModifier -> {
stack.add(next.inner) // 把 inner 部分加入到 stack 中
stack.add(next.outer) // 把 outer 部分加入到 stack 中
}
// 5. 如果 next 是 Modifier.Element 类型,则直接加到 result 中
is Modifier.Element -> result.add(next)
// 6. 如果 next 是 Modifier 的其他实现,可能我们不知道具体实现细节。
// 把 it(可能是 Modifier.Element 的实例)加入到 result 中
else -> next.all {
result.add(it)
true
}
}
}
// 7. 在处理完所有的元素之后,返回 result,它包含了所有的 Modifier.Element
return result
}
这个函数其实就是添加了一个 Modifier 调用栈,以便可以迭代地处理一个可能嵌套的 Modifier 结构。栈的使用是为了在不使用递归的情况下扁平化修饰符的结构。
注意!!!从 1.3.0-beta01 版本开始,Compose 中不再使用 foldIn/foldOut 函数对 Modifier 进行遍历了,在 1.3.0-beta01 之前的版本 LayoutNode 源码中是通过 foldOut 遍历 + 头插法处理,而现在是通过 fillVector 函数处理达到类似的效果。
看的懂吗?是不是很懵?我们来个实际例子吧,比如传入的 Modifier 链如下:
// 这是你写的代码
Box(Modifier.padding(10.dp).size(20.dp))
// 此时 Modifier 链为:
// modifierChain = Modifier.padding(10.dp).size(20.dp)
//
// 实际的结构:
// CombinedModifier(
// PaddingModifier // outer
// SizeModifier // inner
// )
我们来看看 fillVector 是怎么处理它的:
????1. 当调用 fillVector 函数时,初始状态是 modifierChain 放入了一个空的 stack 中。
????2. 此时 stack 不为空,开始循环:
????????· removeAt 从 stack 的末尾(最后加入的元素)移除 CombinedModifier(因为它是链中最后一个元素),并将其赋值给 next。
????????· next 是一个 CombinedModifier 实例,此时就把 inner 和 outer 分别加进 stack 中,这个时候 stack 里面就又有了两个元素:[SizeModifier, PaddingSize],或者说是 [LayoutModifier, LayoutModifier]。
????????· 再次循环,这个时候再次开始 removeAt 从末尾取,先取到 PaddingSize,条件判断发现它是一个 Modifier.Element,那就把它加进 result 里面(result 也是一个可变列表)。
????????· 继续循环,再取到 SizeModifier,发现它也是一个 Modifier.Element,那就直接加进 result 里面。
????3. 继续循环,啪~ stack 没东西了,循环结束,直接返回 result 结果:[PaddingModifier, SizeModifier]。
这套流程我给你画了张图:
现在我们来分析第二段核心代码:
// NodeChain.kt
} else if (before.size == 0) {
attachNeeded = true
coordinatorSyncNeeded = true
var i = after.size - 1
var aggregateChildKindSet = 0
var node = tail // 它就是组件自身,只不过封装成了 Modifier.Node
// 从之前返回的 result 集合里面最后一个元素开始遍历
while (i >= 0) {
val next = after[i] // 最后一个元素( Modifier )
val child = node // 组件自身
node = createAndInsertNodeAsParent(next, child)
...
i--
}
}
看到这里,你大概就清楚 createAndInsertNodeAsParent 要做什么工作了吧?它传入了两个参数:next 和 child。next 不就是你对组件添加的所有的 Modifier 吗?child 一开始不就是组件自身吗?那么 createAndInsertNodeAsParent 函数是不是要开始对 所有的 Modifier 跟组件自身做一些进一步操作了?
现在我们来仔细看看 createAndInsertNodeAsParent 做了什么:
// NodeChain.kt
private fun createAndInsertNodeAsParent(
element: Modifier.Element, // 分解后的 Modifier.Element,例如 PaddingModifier...
child: Modifier.Node, // 一开始为组件本身,例如 Box()
): Modifier.Node {
// 1. 创建一个新的节点
val node = when (element) {
// 2. 如果 element 是 ModifierNodeElement 的实例,它会调用 element 上的 create 方法,
// 这个方法会返回一个新的 Modifier.Node
is ModifierNodeElement<*> -> element.create().also {
it.kindSet = calculateNodeKindSetFrom(it)
}
else -> BackwardsCompatNode(element)
}
...
// 3. 插入到树中
return insertParent(node, child)
}
我们再来看下 insertParent 函数做了什么:
// NodeChain.kt
private fun insertParent(node: Modifier.Node, child: Modifier.Node): Modifier.Node {
// 获取 child 的父节点
val theParent = child.parent
if (theParent != null) { // 一开始没有父节点,跳过
theParent.child = node
node.parent = theParent
}
// 将 child 的父节点设置为新节点(node)
child.parent = node
// 将新节点(node)的子节点设置为 child
node.child = child
// 返回新插入的节点(node)
return node
}
能理解吗?我也给你画了张图:
现在我们来看看 syncCoordinator 函数:
// NodeChain.kt
private fun syncCoordinators() {
var coordinator: NodeCoordinator = innerCoordinator
var node: Modifier.Node? = tail.parent
while (node != null) {
// 遇到 LayoutModifier 的处理
// 创建一个 LayoutModifierNodeCoordinator,也是用来做测量的
// 让其他 Modifier 会受到某一个 LayoutModifier 的限制影响
if (node.isKind(Nodes.Layout) && node is LayoutModifierNode) {
// 如果节点已有关联的 coordinator,则使用现有的协调器,同时判断此协调器所关联的节点是否变化了,
// 如果有变化,则调用 onLayoutModifierNodeChanged() 方法
val next = if (node.coordinator != null) {
val c = node.coordinator as LayoutModifierNodeCoordinator
val prevNode = c.layoutModifierNode
c.layoutModifierNode = node
if (prevNode !== node) c.onLayoutModifierNodeChanged()
c
} else {
// 如果节点没有关联的 coordinator,则新建一个 LayoutModifierNodeCoordinator,
// 并通过 updateCoordinator 方法将这个新协调器和节点相关联。
val c = LayoutModifierNodeCoordinator(layoutNode, node)
node.updateCoordinator(c)
c
}
// 不论是使用旧的还是新建的 coordinator,通过设置 wrappedBy 和 wrapped 属性,
// 建立当前 coordinator 与下一个 coordinator 的包装关系,
// 然后将 coordinator 变量更新为下一个 coordinator
coordinator.wrappedBy = next
next.wrapped = coordinator
coordinator = next
} else {
// 如果 node 不是布局修饰符节点,直接调用 updateCoordinator 方法来更新协调器,
// 将 node 和 NodeCoordinator 挂接关联
node.updateCoordinator(coordinator)
}
// 在每次迭代结束时,将 node 变量更新为当前节点的父节点 node.parent,为下一次循环准备
node = node.parent
}
coordinator.wrappedBy = layoutNode.parent?.innerCoordinator
outerCoordinator = coordinator // 调整外层 NodeCoordinator
}
看懂了吗?我又画了一张图:
简单总结下 updateFrom() 的处理步骤:
我们可以梳理一下:
1. 不设置 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 做综合测量了。
我们看下 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
}
}
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 {}
里面的测量和布局逻辑。