前言
在上一节中,介绍了Modifier的基础知识,从本节开始将会陆陆续续介绍Compose中一些经典的Modifier使用及其原理,帮助我们在之后的自定义view通过理论基础。
1 LayoutModifier基础使用
从字面意思上来看,LayoutModifier
与布局相关,在传统的View体系中,在自定义ViewGroup
时,必须要重写onLayout
方法,用于摆放子view的位置。那么LayoutModifier
其实也是来做这件事的。
我们之前在使用Modifier的时候,像设置宽高、padding等,但凡涉及到大小修改的,都是LayoutModifier
在做处理,例如我们设置宽高,会融合一个SizeModifier
,
@Stable
fun Modifier.size(size: Dp) = this.then(
SizeModifier(
minWidth = size,
maxWidth = size,
minHeight = size,
maxHeight = size,
enforceIncoming = true,
inspectorInfo = debugInspectorInfo {
name = "size"
value = size
}
)
)
而SizeModifier
就是实现了LayoutModifier
接口,它也是一个Element,符合我们之前的结论,任意Modifier都直接或者间接实现了Element接口。
@JvmDefaultWithCompatibility
interface LayoutModifier : Modifier.Element {
/**
* The function used to measure the modifier. The [measurable] corresponds to the
* wrapped content, and it can be measured with the desired constraints according
* to the logic of the [LayoutModifier]. The modifier needs to choose its own
* size, which can depend on the size chosen by the wrapped content (the obtained
* [Placeable]), if the wrapped content was measured. The size needs to be returned
* as part of a [MeasureResult], alongside the placement logic of the
* [Placeable], which defines how the wrapped content should be positioned inside
* the [LayoutModifier]. A convenient way to create the [MeasureResult]
* is to use the [MeasureScope.layout] factory function.
*
* A [LayoutModifier] uses the same measurement and layout concepts and principles as a
* [Layout], the only difference is that they apply to exactly one child. For a more detailed
* explanation of measurement and layout, see [MeasurePolicy].
*/
fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
): MeasureResult
/**
* The function used to calculate [IntrinsicMeasurable.minIntrinsicWidth].
*/
fun IntrinsicMeasureScope.minIntrinsicWidth(
measurable: IntrinsicMeasurable,
height: Int
): Int = MeasuringIntrinsics.minWidth(
this@LayoutModifier,
this,
measurable,
height
)
/**
* The lambda used to calculate [IntrinsicMeasurable.minIntrinsicHeight].
*/
fun IntrinsicMeasureScope.minIntrinsicHeight(
measurable: IntrinsicMeasurable,
width: Int
): Int = MeasuringIntrinsics.minHeight(
this@LayoutModifier,
this,
measurable,
width
)
/**
* The function used to calculate [IntrinsicMeasurable.maxIntrinsicWidth].
*/
fun IntrinsicMeasureScope.maxIntrinsicWidth(
measurable: IntrinsicMeasurable,
height: Int
): Int = MeasuringIntrinsics.maxWidth(
this@LayoutModifier,
this,
measurable,
height
)
/**
* The lambda used to calculate [IntrinsicMeasurable.maxIntrinsicHeight].
*/
fun IntrinsicMeasureScope.maxIntrinsicHeight(
measurable: IntrinsicMeasurable,
width: Int
): Int = MeasuringIntrinsics.maxHeight(
this@LayoutModifier,
this,
measurable,
width
)
}
1.1 Modifier.layout使用
/**
* Creates a [LayoutModifier] that allows changing how the wrapped element is measured and laid out.
*
* This is a convenience API of creating a custom [LayoutModifier] modifier, without having to
* create a class or an object that implements the [LayoutModifier] interface. The intrinsic
* measurements follow the default logic provided by the [LayoutModifier].
*
* Example usage:
*
* @sample androidx.compose.ui.samples.ConvenienceLayoutModifierSample
*
* @see androidx.compose.ui.layout.LayoutModifier
*/
fun Modifier.layout(
measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) = this then LayoutModifierElement(measure)
按照惯例,看下官方的解释:
Modifier.layout用于创建一个LayoutModifier,并允许改变被包装的元素的测量和布局方式
所以,Modifier.layout
的核心作用就是用来测量和布局用的,它会创建一个LayoutModifier与之前 的Modifier融合,既然layout
函数能够改变布局的方式,那么我们可以试一下。
首先在使用Modifier.layout
时,有一个回调函数measure
,其中两个参数Measurable
和Constraints
,
/**
* A part of the composition that can be measured. This represents a layout.
* The instance should never be stored.
*/
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
}
其中Measurable
可以认为是一个布局,甚至你可以认为它就是调用这个函数的组件。 它内部有一个函数measure
,用于测量这个布局,返回值为Placeable
,其内部存储了布局的size以及操作子view位置的能力,通过Placeable
可以拿到布局的大小。
另一个参数Constraints
,可以认为是约束条件,如同传统View体系中的ConstraintsLayout
,用于控制组件的大小,例如设置最大宽度、最大高度等等,在测量的过程中会因为这些约束条件从而改变布局的方式,例如Text设置了宽度为300,但是maxWidth为200,那么最终Text的宽度就是200.
/**
* Interface holding the size and alignment lines of the measured layout, as well as the
* children positioning logic.
* [placeChildren] is the function used for positioning children. [Placeable.placeAt] should
* be called on children inside [placeChildren].
* The alignment lines can be used by the parent layouts to decide layout, and can be queried
* using the [Placeable.get] operator. Note that alignment lines will be inherited by parent
* layouts, such that indirect parents will be able to query them as well.
*/
interface MeasureResult {
val width: Int
val height: Int
val alignmentLines: Map<AlignmentLine, Int>
fun placeChildren()
}
measure
回调的返回值是MeasureResult
,他也是一个接口,用来持有测量之后的布局宽高以及alignmentLines
基线对齐线,还有一个函数placeChildren
,它是用来摆放组件的位置,Placeable.placeAt
函数只能在placeChildren
中执行。
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
)
}
}
当然,我们在measure
回调不需要自己创建MeasureResult
接口实例对象,Compose中提供了layout
函数,这个函数返回值就是MeasureResult
接口实例对象,这个函数第4个参数placementBlock
同样可以用来处理view的放置逻辑。
@Composable
fun ModifierLayout() {
Text(
text = "Layout",
color = Color.Red,
fontWeight = FontWeight(600),
fontSize = 20.sp,
modifier = Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
Log.d("TAG", "ModifierLayout: width=${placeable.width} height=${placeable.height}")
layout(placeable.width,placeable.height){
//处理子view的摆放逻辑
}
})
}
如果是这样直接运行,Text是无法展示出来的,因为现在我们添加了Modifier.layout属性,相当于接管了系统的layout操作,因此必须要设置Text的摆放逻辑。
@Composable
fun ModifierLayout() {
Text(
text = "Layout",
color = Color.Red,
fontWeight = FontWeight(600),
fontSize = 20.sp,
modifier = Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
Log.d("TAG", "ModifierLayout: width=${placeable.width} height=${placeable.height}")
layout(placeable.width,placeable.height){
placeable.placeRelative(0,0)
}
})
}
这里我们将view的位置摆放在屏幕左上角,运行之后就正常显示出来。
其实从整个Modifier.layout
看下来,其实跟传统的自定义View还是挺像的:
graph LR
Modifier.layout --> Measurable.measure --> MeasureScope.layout --> Placeable.placexxx
也是一个measure到layout的流程,但是在Compose中的layout,准确的来说是Modifier中的layout,仅仅只能修改当前组件的大小以及摆放位置;如果想实现ViewGroup
中的onLayout,即多个组件的摆放Logic,需要使用Layout。
关于Layout
的使用,会在自定义view的章节中完整介绍。
1.2 Placeable介绍
接下来看一下Placeable这个类,看下官方解释:
Placeable是Measurable.measure的结果,主要用于通过其父布局定子组件的位置
总体来说,Placeable
就是一个用来定位组件位置的类,相当于传统view中layout的作用。
/**
* A [Placeable] corresponds to a child layout that can be positioned by its
* parent layout. Most [Placeable]s are the result of a [Measurable.measure] call.
*
* A `Placeable` should never be stored between measure calls.
*/
abstract class Placeable : Measured {
// 组件的宽度
var width: Int = 0
private set
// 组件的高度
var height: Int = 0
private set
// ......
abstract class PlacementScope {
fun Placeable.placeRelative(position: IntOffset, zIndex: Float = 0f) =
placeAutoMirrored(position, zIndex, null)
fun Placeable.placeRelative(x: Int, y: Int, zIndex: Float = 0f) =
placeAutoMirrored(IntOffset(x, y), zIndex, null)
fun Placeable.place(x: Int, y: Int, zIndex: Float = 0f) =
placeApparentToRealOffset(IntOffset(x, y), zIndex, null)
fun Placeable.place(position: IntOffset, zIndex: Float = 0f) =
placeApparentToRealOffset(position, zIndex, null)
// ......
}
对于Placeable
可以获取组件的宽高就不说了,看下PlacementScope
中的几个函数,均为Placeable
中的扩展函数:
- placeRelative
placeRelative带有相对的含义,坐标x,y位置是相对于父布局的左上角,而非屏幕的左上角,而且如果父布局的方向是从右向左,那么使用placeRelative
也会镜像地摆放在父布局的右上角。
- place
place则是一个没有相对位置概念的函数,坐标x,y位置同样是相对于父布局的左上角,而非屏幕的左上角,但如果父布局的方向是从右向左,那么使用placeRelative
依然会将组件摆放在左上角,不会做镜像处理。
所以两者区别就是是否适应RTL布局。
1.3 使用场景
从前面的介绍中,我们大概知道,使用Modifier的layout
函数可用于修改组件的尺寸,或者组件的摆放位置,仅此而已,像Text组件中的文字摆放顺序,布局容器中的组件摆放顺序,使用layout是完全无法干预的。 因此layout仅仅是对组件的大小和位置做装饰.
@Composable
fun ModifierLayout() {
Row(
modifier = Modifier
.background(Color.Green)
.width(200.dp)
) {
Text(
text = "Layout",
color = Color.Red,
fontWeight = FontWeight(600),
fontSize = 20.sp,
modifier = Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
val maxSize = maxOf(placeable.width, placeable.height)
layout(maxSize, maxSize) {
placeable.placeRelative(0, maxSize / 4)
}
})
}
}
例如在对Text
测量完成之后,希望将组件的宽高设置为Text
的宽高最大值,并且摆放位置希望居中,这个时候就可以使用Modifier的layout
函数,而如果想要进一步修改Text
内部的具体布局实现,请使用Layout
函数。
2 LayoutModifier底层原理
在前面介绍Modifier.layout
时说过,它会创建一个LayoutModifier
对象。
fun Modifier.layout(
measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) = this then LayoutModifierElement(measure)
从源码看到,最终创建的是LayoutModifierElement
,他其实就是一个LayoutModifier
。
@OptIn(ExperimentalComposeUiApi::class)
private data class LayoutModifierElement(
val measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) : ModifierNodeElement<LayoutModifierImpl>() {
override fun create() = LayoutModifierImpl(measure)
override fun update(node: LayoutModifierImpl) = node.apply {
measureBlock = measure
}
override fun InspectorInfo.inspectableProperties() {
name = "layout"
properties["measure"] = measure
}
}
回到Compose的整体,当我们在创建页面布局时,会通过一系列的@Composable
函数组合,例如:
Column(modifier = Modifier.background(Color.Green)) {
Text(text = "文本", modifier = Modifier.clickable { })
Box {
Button(onClick = { /*TODO*/ }) {
Text(text = "按钮")
}
}
}
每个@Composable
组件在Compose的UI体系中,都对应一个LayoutNode
,也就是说Compose的布局体系中,Compose的界面树其实是一棵LayoutNode树,在measure和layout的时候,操控的就是LayoutNode。
stateDiagram-v2
Column --> Text
Column --> Box
Box --> Button
LayoutNode(Column) --> LayoutNode(Text)
LayoutNode(Column) --> LayoutNode(Box)
LayoutNode(Box) --> LayoutNode(Button)
在每一个LayoutNode
中,都有一个modifier
成员变量,这个Modifier其实就是我们在Column或者Box中定义的Modifier。
/**
* The [Modifier] currently applied to this node.
*/
override var modifier: Modifier = Modifier
set(value) {
require(!isVirtual || modifier === Modifier) {
"Modifiers are not supported on virtual LayoutNodes"
}
field = value
// 更新链表数据
nodes.updateFrom(value)
// TODO(lmr): we don't need to do this every time and should attempt to avoid it
// whenever possible!
forEachCoordinatorIncludingInner {
it.updateLookaheadScope(mLookaheadScope)
}
layoutDelegate.updateParentData()
}
当我们设置Modifier的时候,其实就是执行了LayoutNode
中modifier
的set函数。在set方法中,会执行一个非常重要的操作,就是执行NodeChain
的updateFrom
函数,从字面意思上看就是根据设置的Modifier
更新LayoutNode
节点的Modifier。
2.1 NodeChain
先看下NodeChain
的源码,在NodeChain
的构造方法中,有一个LayoutNode
成员变量,可以认为就是Compose树中的每一个组件,或者具体来说就是每一个View,可以看下本小节开头的关系图:
stateDiagram-v2
LayoutNode(Column) --> LayoutNode(Text)
LayoutNode(Column) --> LayoutNode(Box)
LayoutNode(Box) --> LayoutNode(Button)
在NodeChain
中持有一个Compose组件的引用,看下几个关键的成员变量:
-
innerCoordinator
可以认为是每个LayoutNode中的Modifier.Node的统筹管理者,
Modifier.Node
可以认为是Modifier.Element
转换后的结果,属于在Modifier链中的节点。 -
tail
和head
从字面意思上看,就是
Modifier.Node
的头部和尾部,这里头部和尾部指针,都指向了尾部,所以NodeChain是一个双向链表。
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
private val isUpdating: Boolean get() = head === SentinelHead
private val aggregateChildKindSet: Int get() = head.aggregateChildKindSet
private var current: MutableVector<Modifier.Element>? = null
private var buffer: MutableVector<Modifier.Element>? = null
private var cachedDiffer: Differ? = null
private var logger: Logger? = null
internal fun useLogger(logger: Logger?) {
this.logger = logger
}
private fun padChain() {
check(head !== SentinelHead)
val currentHead = head
currentHead.parent = SentinelHead
SentinelHead.child = currentHead
head = SentinelHead
}
private fun trimChain() {
check(head === SentinelHead)
head = SentinelHead.child ?: tail
head.parent = null
SentinelHead.child = null
check(head !== SentinelHead)
}
/**
* This method will update the node chain based on the provided modifier chain. This method is
* responsible for calling all appropriate lifecycles for nodes that are
* created/disposed/updated during this call.
*
* This method will attempt to optimize for the common scenario of the modifier chain being of
* equal size and each element being able to be reused from the prior one. In most cases this
* is what recomposition will result in, provided modifiers weren't conditionally provided. In
* the cases where the modifier is not of equal length to the prior value, or modifiers of
* different reuse types ended up in the same position, this method will deopt into a slower
* path which will perform a diff on the modifier chain and execute a minimal number of
* insertions and deletions.
*/
internal fun updateFrom(m: Modifier) {
// If we run the diff and there are no new nodes created, then we don't need to loop through
// and run the attach cycle on them. We simply keep track of this during the diff to avoid
// this overhead at the end if we can, since it should be fairly common.
var attachNeeded = false
// If we run the diff and there are no structural changes, we can avoid looping through the
// list and updating the coordinators. We simply keep track of this during the diff to avoid
// this overhead at the end if we can, since it should be fairly common. Note that this is
// slightly different from [attachNeeded] since a node can be updated and return null or a
// new instance which is perfectly valid and would require a new attach cycle, however the
// coordinator would be identical and so [attachNeeded] would be true but this false
var coordinatorSyncNeeded = false
// Use the node chain itself as a head/tail temporarily to prevent pruning the linkedlist
// to the point where we don't have reference to it. We need to undo this at the end of
// this method.
padChain()
// 第一步:创建两个数组
val before = current ?: MutableVector(capacity = 0)
val after = m.fillVector(buffer ?: mutableVectorOf())
if (after.size == before.size) {
// assume if the sizes are the same, that we are in a common case of no structural
// changes we will attempt an O(n) fast-path diff and exit if a diff is detected, and
// do the O(N^2) diff beyond that point
val size = before.size
// for the linear diff we want to start with the "unpadded" tail
var node: Modifier.Node? = tail.parent
var i = size - 1
var aggregateChildKindSet = 0
while (node != null && i >= 0) {
val prev = before[i]
val next = after[i]
when (actionForModifiers(prev, next)) {
ActionReplace -> {
// TODO(lmr): we could avoid running the diff if i = 0, since that would
// always be simple remove + insert
// structural change!
// back up one for the structural diff algorithm. This should be safe since
// our chain is padded with the EmptyHead/EmptyTail nodes
logger?.linearDiffAborted(i, prev, next, node)
i++
node = node.child
break
}
ActionUpdate -> {
// this is "the same" modifier, but some things have changed so we want to
// reuse the node but also update it
val beforeUpdate = node
node = updateNodeAndReplaceIfNeeded(prev, next, beforeUpdate)
logger?.nodeUpdated(i, i, prev, next, beforeUpdate, node)
}
ActionReuse -> {
logger?.nodeReused(i, i, prev, next, node)
// no need to do anything, this is "the same" modifier
}
}
// if the node is new, we need to run attach on it
if (!node.isAttached) attachNeeded = true
aggregateChildKindSet = aggregateChildKindSet or node.kindSet
node.aggregateChildKindSet = aggregateChildKindSet
node = node.parent
i--
}
if (i > 0) {
check(node != null)
attachNeeded = true
coordinatorSyncNeeded = true
// there must have been a structural change
// we only need to diff what is left of the list, so we use `i` as the "beforeSize"
// and "afterSize"
structuralUpdate(
before,
i,
after,
i,
// its important that the node we pass in here has an accurate
// "aggregateChildMask"
node,
)
}
} else if (before.size == 0) {
// common case where we are initializing the chain and the previous size is zero. In
// this case we just do all inserts. Since this is so common, we add a fast path here
// for this condition.
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)
logger?.nodeInserted(0, i, next, child, node)
aggregateChildKindSet = aggregateChildKindSet or node.kindSet
node.aggregateChildKindSet = aggregateChildKindSet
i--
}
} else if (after.size == 0) {
// common case where we we are removing all the modifiers.
coordinatorSyncNeeded = true
var i = before.size - 1
// for the linear traversal we want to start with the "unpadded" tail
var node: Modifier.Node? = tail.parent
while (node != null && i >= 0) {
logger?.nodeRemoved(i, before[i], node)
val parent = node.parent
detachAndRemoveNode(node)
node = parent
i--
}
} else {
attachNeeded = true
coordinatorSyncNeeded = true
structuralUpdate(
before,
before.size,
after,
after.size,
tail,
)
}
current = after
// clear the before vector to allow old modifiers to be Garbage Collected
buffer = before.also { it.clear() }
trimChain()
if (coordinatorSyncNeeded) {
syncCoordinators()
}
if (attachNeeded && layoutNode.isAttached) {
attach(performInvalidations = true)
}
}
// .....
}
看一下updateFrom
函数,假设目前组件的Modifier如下:
Modifier
.background(LocalBackground.current)
.size(10.dp)
.align(Alignment.Center)
graph LR
background --> size --> align
(1)创建2个数组before
和after
,current
是指当前LayoutNode
的Modifier.Node
合集,如上述流程图所示,如果是第一次加载该组件,那么默认就是空的;after
是指对当前传入的Modifier
做了一次处理,看下fillVector
函数。
private fun Modifier.fillVector(
result: MutableVector<Modifier.Element>
): MutableVector<Modifier.Element> {
// 创建一个数组,先将传入的Modifier加进去。
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)
// some other androidx.compose.ui.node.Modifier implementation that we don't know about...
else -> next.all {
result.add(it)
true
}
}
}
return result
}
在这个函数中会遍历所有的Modifier.Element
,如果是CombinedModifier
类型,这种是最常见的,在Modifier链式调用中,都会通过CombinedModifier
来融合,注意stack中加入的顺序,是inner
先加入,outer
后加入,即新的Modifier
先进,调用方后进入,然后在取元素的时候,先从栈底取,也就是普通的Modifier.Element,其实fillVector
等效于Modifier.Element
的foldIn
顺序遍历,将Modifier(可能存在嵌套)平铺展开
现在我们大概清楚before
和after
两个数组的含义,分别代表:当前LayoutNode的modifier element数组以及传入的的Modifier element数组。
(2)接下来分3种情况:
-
before和after的size一致,说明之前已经给组件设置过Modifier,而且数量级没有发生改变,因此只需要处理更新、替换、复用的操作即可,这里我就不再做过多的赘述了;
-
当before为空时,这是我要重点讲的,因为这是第一次设置Modifier时流程。此时在after数组中,存储的顺序为:
graph LR background --> size --> align
在初始化中的流程,是从
after
数组中最后一个元素开始遍历,也就是align
属性,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) logger?.nodeInserted(0, i, next, child, node) aggregateChildKindSet = aggregateChildKindSet or node.kindSet node.aggregateChildKindSet = aggregateChildKindSet i-- }
每次创建都是执行
createAndInsertNodeAsParent
函数,在这个函数中会判断当前Modifier.Element
的属性,如果是ModifierNodeElement
,例如LayoutModifier
,那么就执行其create
函数,创建LayoutModifierImpl
;如果是普通的Modifier.Element
,则将其封装为BackwardsCompatNode
.private fun createAndInsertNodeAsParent( element: Modifier.Element, child: Modifier.Node, ): Modifier.Node { val node = when (element) { is ModifierNodeElement<*> -> element.create().also { it.kindSet = calculateNodeKindSetFrom(it) } else -> BackwardsCompatNode(element) } check(!node.isAttached) node.insertedNodeAwaitingAttachForInvalidation = true return insertParent(node, child) }
上述的过程,其实就是将Modifier Element转换为Modifier Node的过程。 然后执行
insertParent
,我们关注下两个入参:node:Modifier.Node,其实就是实例化的过程,对应了
ModifierNodeElement
和普通的Modifier.Element
。child:第一次传进来的是tail,也就是
innerCoordinator
的tail,可以认为就是组件自身。添加的过程属于头插法, tail的父节点设置为了传进来的node,也就是
algin
属性,后续进来的Modifier.Node
将会继续头插,直到遍历完成。/** * This inserts [node] as the parent of [child] in the current linked list. * For example: * * Head... -> child -> ...Tail * * gets transformed into a list of the following shape: * * Head... -> node -> child -> ...Tail * * @return The inserted [node] */ private fun insertParent(node: Modifier.Node, child: Modifier.Node): Modifier.Node { val theParent = child.parent if (theParent != null) { theParent.child = node node.parent = theParent } // 核心代码: child.parent = node node.child = child return node }
看一下下面的图,我们梳理下整个初始化的流程:
Modifier
.background(LocalBackground.current)
.size(10.dp)
.align(Alignment.Center)
Modifier.fillVector
过程:
graph LR
background --> size --> align
头插的过程:
2.2 同步过程
在初始化完成之后,因为标志位coordinatorSyncNeeded
设置为了true,因此需要进行同步的过程,在这个函数中:
coordinator
:其实对应的是innerCoordinator
,可以理解为组件自身,因为它是在NodeChain中初始化的,每个LayoutNode
都有一个NodeChain
。
node
:它是tail的parent节点,从2.1小节末尾的图可以知道,就是aligin
对应的Modifier.Node
.
private fun syncCoordinators() {
var coordinator: NodeCoordinator = innerCoordinator
// tail尾部节点的parent就是
var node: Modifier.Node? = tail.parent
while (node != null) {
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
}
// 关注下核心代码
// wrappedBy指的是外部的coordinator
coordinator.wrappedBy = next
// wrapped指的是内部的coordinator
next.wrapped = coordinator
coordinator = next
} else {
node.updateCoordinator(coordinator)
}
node = node.parent
}
coordinator.wrappedBy = layoutNode.parent?.innerCoordinator
// 同步完成之后,将outerCoordinator的值设置为了coordinator
outerCoordinator = coordinator
}
如果当前Node为LayoutModifierNode
,那么会将Modifier.Node
包上一层NodeCoordinator
,具体怎么包的,看下核心代码:
coordinator
就是innerCoordinator
,它被next
也就是LayoutModifierNodeCoordinator
包裹,next
包裹着innerCoordinator
,然后将coordinator
指向LayoutModifierNodeCoordinator
。
如果当前Modifier不是LayoutModifierNode
,那么会共用上一个LayoutModifierNodeCoordinator
,因为只是调用了更新函数updateCoordinator
,例如DrawModifier
或者ParentDataModifier
,之所以这么做是因为在layout
和draw
的时候需要做区分。
在同步完成之后,会将coordinator
给outerCoordinator
赋值,也就是说将最外层的LayoutModifierNodeCoordinator
给予outerCoordinator
。 这里需要关注下wrapped
和wrappedBy
对象,后续源码介绍会用到。
2.3 remeasure
在设置完成Modifier之后,接下来就需要根据这些配置信息测量组件的大小以及摆放位置,此时会执行到LayoutNode的remeasure
函数。
/**
* Return true if the measured size has been changed
*/
internal fun remeasure(
constraints: Constraints? = layoutDelegate.lastConstraints
): Boolean {
return if (constraints != null) {
if (intrinsicsUsageByParent == UsageByParent.NotUsed) {
// This LayoutNode may have asked children for intrinsics. If so, we should
// clear the intrinsics usage for everything that was requested previously.
clearSubtreeIntrinsicsUsage()
}
measurePassDelegate.remeasure(constraints)
} else {
false
}
}
最终执行的是outerCoordinator
的measure函数,在2.2小节中,我们介绍过outerCoordinator
其实就是最外层的LayoutModifierNodeCoordinator
,
/**
* Performs measure with the given constraints and perform necessary state mutations before
* and after the measurement.
*/
private fun performMeasure(constraints: Constraints) {
check(layoutState == LayoutState.Idle) {
"layout state is not idle before measure starts"
}
layoutState = LayoutState.Measuring
measurePending = false
layoutNode.requireOwner().snapshotObserver.observeMeasureSnapshotReads(
layoutNode,
affectsLookahead = false
) {
// 这里做真正的测量
outerCoordinator.measure(constraints)
}
// The resulting layout state might be Ready. This can happen when the layout node's
// own modifier is querying an alignment line during measurement, therefore we
// need to also layout the layout node.
if (layoutState == LayoutState.Measuring) {
markLayoutPending()
layoutState = LayoutState.Idle
}
}
真正做测量的就是LayoutModifierNodeCoordinator
的measure函数。
// LayoutModifierNodeCoordinator.kt
override fun measure(constraints: Constraints): Placeable {
performingMeasure(constraints) {
with(layoutModifierNode) { // scope:LayoutModifierNode
measureResult = measure(wrappedNonNull, constraints)
this@LayoutModifierNodeCoordinator
}
}
onMeasured()
return this
}
在measure中,通过with函数提供了LayoutModifierNode
上下文,其实在LayoutModifier
在转换的过程中会创建LayoutModifierImpl
,它其实就是一个Modifier.Node
,会执行其measure函数,其实最终还是执行了block中的函数,也就是说执行了我们自定义的测量流程,所以这样就能够影响系统底层测量的过程。
@OptIn(ExperimentalComposeUiApi::class)
internal class LayoutModifierImpl(
var measureBlock: MeasureScope.(Measurable, Constraints) -> MeasureResult
) : LayoutModifierNode, Modifier.Node() {
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
) = measureBlock(measurable, constraints)
override fun toString(): String {
return "LayoutModifierImpl(measureBlock=$measureBlock)"
}
}
2.3 Modifier的顺序敏感
首先,我们在前面的介绍中,在同步Coordinator
的时候,会从tail
开始,依次拿到其parent,判断是否为layout
类型,如果是就会构建LayoutModifierNodeCoordinator
;如果不是layout
类型,那么就会复用上一个LayoutModifierNodeCoordinator
。
如下面这个例子,我以Text
中的Modifier为例:
Box(
Modifier
.background(Color.Blue)
.size(100.dp)
) {
Text(text = "按钮",
Modifier
.background(Color.Green)
.padding(10.dp)
.clickable {
}
.padding(20.dp)
.align(Alignment.Center)
)
}
tail
就是Text,它是一个LayoutNode
,同时内部拥有一个NodeChain
,那么开始同步的时候,从align
开始,因为padding(20.dp)
属于LayoutModifier
,那么会新建一个LayoutModifierNodeCoordinator
,clickable
不属于layout
类型,那么就会复用padding(20.dp)
,接着padding(10.dp)
又会新建一个LayoutModifierNodeCoordinator
。
所以在测量的时候,outerCoordinator
就是padding(10.dp)
对应的NodeCoordinator,那么这块区域不会被设置监听;而测量到padding(20.dp)
时,会发现clickable
也复用了这块区域,那么点击事件的响应也只会加到20dp上,而不会加到后续的10dp范围上,例如下图展示:
如果想要全部响应,那么clickable事件需要在所有的padding事件设置之前就定义好。