Modifier 系列的前几篇文章中,已经将 LayoutModifier 以及 Before LayoutModifier 阶段的 4 个 Modifier 都讲解完了,本篇文章我们把 After LayoutModifier 阶段的几个 Modifier 过一遍。从 EntityList 的 addAfterLayoutModifier() 中可以看到有 3 个 Modifier:
/**
* Add [LayoutNodeEntity] values that must be added after the LayoutModifier.
*/
@OptIn(ExperimentalComposeUiApi::class)
fun addAfterLayoutModifier(layoutNodeWrapper: LayoutNodeWrapper, modifier: Modifier) {
if (modifier is OnPlacedModifier) {
add(SimpleEntity(layoutNodeWrapper, modifier), OnPlacedEntityType.index)
}
if (modifier is OnRemeasuredModifier) {
add(SimpleEntity(layoutNodeWrapper, modifier), RemeasureEntityType.index)
}
if (modifier is LookaheadOnPlacedModifier) {
add(SimpleEntity(layoutNodeWrapper, modifier), LookaheadOnPlacedEntityType.index)
}
}
除此之外,我们还会介绍 OnGloballyPositionedModifier 以及 ModifierLocal 作为 Modifier 系列的收尾。
1、LayoutModifier 的 Before 与 After 阶段
前面我们说过,在 LayoutNode 的 modifier 属性的 set() 中,使用 foldOut() 遍历 Modifier 调用链时,会以 LayoutModifier 为界,在它之前以及之后将 Modifier 存储到 LayoutNodeWrapper 的 entities 数组的对应类型的 LayoutNodeEntity 的链表头:
override var modifier: Modifier = Modifier
set(value) {
...
// Create a new chain of LayoutNodeWrappers, reusing existing ones from wrappers
// when possible.
val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap ->
...
// 先添加到链表中的 Modifier
toWrap.entities.addBeforeLayoutModifier(toWrap, mod)
...
// 遇到 LayoutModifier 就用 ModifiedLayoutNode 包装当前最外层的 LayoutNodeWrapper
val wrapper = if (mod is LayoutModifier) {
// Re-use the layoutNodeWrapper if possible.
(reuseLayoutNodeWrapper(toWrap, mod)
?: ModifiedLayoutNode(toWrap, mod)).apply {
onInitialize()
updateLookaheadScope(mLookaheadScope)
}
} else {
toWrap
}
// 后添加到链表中的 Modifier
wrapper.entities.addAfterLayoutModifier(wrapper, mod)
wrapper
}
setModifierLocals(value)
outerWrapper.wrappedBy = parent?.innerLayoutNodeWrapper
layoutDelegate.outerWrapper = outerWrapper
...
}
之所以用 addBeforeLayoutModifier() 与 addAfterLayoutModifier() 区分出添加到链表中的顺序,并不是在指定处理 Modifier 的先后顺序,而是在同一个 Modifier 对象具有多重“身份”时,先/后处理哪个“身份”。
具体点说,由于一个 Modifier 对象可以实现多个 Modifier 接口,比如既可以实现 DrawModifier,也可以实现 OnRemeasuredModifier,此时该 Modifier 有多重身份,那么它作为 DrawModifier 时,就会在 addBeforeLayoutModifier() 内先于 LayoutModifier 被处理,作为 OnRemeasuredModifier 时,就在 addAfterLayoutModifier() 内后于 LayoutModifier 被处理。
通过 addBeforeLayoutModifier() 被添加的 mod 是添加到当前最外层的 LayoutNodeWrapper —— toWrap 中。但经过 LayoutModifier 的处理时,原本最外层的 LayoutNodeWrapper 被新的 ModifiedLayoutNode 包装成次外层的 LayoutNodeWrapper 了,后面调用 addAfterLayoutModifier() 再添加 mod 就是添加到新的最外层 ModifiedLayoutNode 中了。实际上就相当于在遍历时,每遇到一个 LayoutModifier 之后,再添加的 Modifier 都是添加到这个 LayoutModifier 对应的 ModifiedLayoutNode 中,直到遇到下一个 LayoutModifier。
以 LayoutModifier 为基准视角的话,先添加的 Modifier 是被添加到表示它的 ModifiedLayoutNode 的内层的 LayoutNodeWrapper 中,而后添加的 Modifier 是被添加到 ModifiedLayoutNode 本身的链表中:
ModifiedLayoutNode(
entities = [后添加的 Modifier 的链表数组],
modifier = LayoutModifier,
// 这个 LayoutNodeWrapper 的实际类型要根据 Modifier 链的具体情况,
// 它可能是最内层的 InnerPlaceable,也可能还是一个 ModifiedLayoutNode
wrapped = LayoutNodeWrapper(
entities = [先添加的 Modifier 的链表数组]
)
)
在当前版本中,唯一一个实现了多个 Modifier 的是 PainterModifier,它实现了 LayoutModifier 与 DrawModifier:
private class PainterModifier(
val painter: Painter,
val sizeToIntrinsics: Boolean,
val alignment: Alignment = Alignment.Center,
val contentScale: ContentScale = ContentScale.Inside,
val alpha: Float = DefaultAlpha,
val colorFilter: ColorFilter? = null,
inspectorInfo: InspectorInfo.() -> Unit
) : LayoutModifier, DrawModifier, InspectorValueInfo(inspectorInfo)
根据上面的处理原则,DrawModifier 要放在 LayoutModifier 所形成的 ModifiedLayoutNode 的内层节点上:
ModifiedLayoutNode(
entities = [],
modifier = LayoutModifier,
// 这个 LayoutNodeWrapper 的实际类型要根据 Modifier 链的具体情况,
// 它可能是最内层的 InnerPlaceable,也可能还是一个 ModifiedLayoutNode
wrapped = LayoutNodeWrapper(
entities = [DrawModifier]
)
)
不过这种设计在 Compose 的 1.3.0-beta1 版本中被修改了,不再区分同一个 Modifier 的多个身份了,都当成一个变量存储,改成了 LayoutModifier 与 DrawModifier 都在 ModifiedLayoutNode 的同一层了。后续如果有机会的话,我们会再解析新版本中 Modifier 的源码实现。
2、OnRemeasuredModifier
2.1 基本使用
OnRemeasuredModifier 的作用类似于 View 体系下的 onMeasure()。当它修饰的组件或者说它修饰的 LayoutModifier 在测量完成时会回调其内部的唯一函数 onRemeasured() 通知测量已经完成:
/**
* A modifier whose onRemeasured is called when the layout content is remeasured.
* The most common usage is onSizeChanged.
*/
@JvmDefaultWithCompatibility
interface OnRemeasuredModifier : Modifier.Element {
/**
* Called after a layout's contents have been remeasured.
*/
fun onRemeasured(size: IntSize)
}
Compose 提供的 OnRemeasuredModifier 的唯一实现类是 OnSizeChangedModifier,它相对于 OnRemeasuredModifier 的功能做出了一些优化,并不会在每一次组件测量完成时都进行回调,只会在测量完成后,尺寸发生变化的情况下回调。使用方法是调用 Modifier.onSizeChanged():
@Stable
fun Modifier.onSizeChanged(
onSizeChanged: (IntSize) -> Unit
) = this.then(
OnSizeChangedModifier(
onSizeChanged = onSizeChanged,
inspectorInfo = debugInspectorInfo {
name = "onSizeChanged"
properties["onSizeChanged"] = onSizeChanged
}
)
)
OnSizeChangedModifier 实现 onRemeasured() 时,只有在新测量的 size 与测量前的 previousSize 不同时,才回调参数上的 onSizeChanged:
private class OnSizeChangedModifier(
val onSizeChanged: (IntSize) -> Unit,
inspectorInfo: InspectorInfo.() -> Unit
) : OnRemeasuredModifier, InspectorValueInfo(inspectorInfo) {
private var previousSize = IntSize(Int.MIN_VALUE, Int.MIN_VALUE)
override fun onRemeasured(size: IntSize) {
if (previousSize != size) {
onSizeChanged(size)
previousSize = size
}
}
}
假如你就是想在每次测量完成后都回调,那么就使用匿名对象的方式实现 OnRemeasuredModifier 即可:
Modifier.then(object : OnRemeasuredModifier {
override fun onRemeasured(size: IntSize) {
TODO("Not yet implemented")
}
})
2.2 原理
OnRemeasuredModifier 接口的 onRemeasured() 会在 LayoutNodeWrapper 进行测量时被调用。这个测量过程也是实现的接口函数:
/**
* 可以被测量的组合体(composition)的一部分,它表示一个布局(layout),其实例不应被存储
*/
interface Measurable : IntrinsicMeasurable {
/**
* 使用 [constraints] 进行测量,返回一个拥有新尺寸的 [Placeable] 布局,一个 [Measurable]
* 在布局过程中只能被测量一次。
*/
fun measure(constraints: Constraints): Placeable
}
LayoutNodeWrapper 实现了该接口,就具备了对布局进行测量的功能:
internal abstract class LayoutNodeWrapper(
internal val layoutNode: LayoutNode
) : LookaheadCapablePlaceable(), Measurable, LayoutCoordinates, OwnerScope,
(Canvas) -> Unit {
}
由于 LayoutNodeWrapper 是个抽象类,并没有直接实现 Measurable,而是交给了它的两个子类 InnerPlaceable 与 ModifiedLayoutNode,所以我们来看子类的实现:
// InnerPlaceable.kt:
override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
// before rerunning the user's measure block reset previous measuredByParent for children
layoutNode.forEachChild {
it.measuredByParent = LayoutNode.UsageByParent.NotUsed
}
measureResult = with(layoutNode.measurePolicy) {
layoutNode.measureScope.measure(layoutNode.childMeasurables, constraints)
}
onMeasured()
return this
}
// ModifiedLayoutNode.kt:
override fun measure(constraints: Constraints): Placeable {
performingMeasure(constraints) {
with(modifier) {
measureResult = measureScope.measure(wrapped, constraints)
this@ModifiedLayoutNode
}
}
onMeasured()
return this
}
两个子类都是在测量完成,得到测量结果 measureResult 后,调用了 onMeasured(),该函数由父类 LayoutNodeWrapper 实现:
fun onMeasured() {
// 如果当前 LayoutNodeWrapper 的 entities 数组中 RemeasureEntity 的链表不为空
if (entities.has(EntityList.RemeasureEntityType)) {
Snapshot.withoutReadObservation {
// 调用 RemeasureEntity 链表上每一个 RemeasureEntity 内封装的
// OnRemeasuredModifier 的 onRemeasured() 函数
entities.forEach(EntityList.RemeasureEntityType) {
it.modifier.onRemeasured(measuredSize)
}
}
}
}
注释中已经写明,onMeasured() 就是执行 RemeasureEntity 链表上所有 OnRemeasuredModifier 的 onRemeasured(),参数上的 measuredSize 来自于测量结果的 Placeable 的测量尺寸。
结合具体例子来说:
Modifier
.padding(20.dp)
.then(object : OnRemeasuredModifier {
override fun onRemeasured(size: IntSize) {
TODO("Not yet implemented")
}
})
.padding(40.dp)
在最右侧的 padding(40.dp) 所隐含的 LayoutModifier 在视图树中所形成的 ModifiedLayoutNode 完成测量后,调用 onMeasured() 时,会执行该 ModifiedLayoutNode 的 RemeasureEntity 链表上的 OnRemeasuredModifier 的 onRemeasured(),then() 内的匿名 OnRemeasuredModifier 对象刚好在适用范围内,因此 onRemeasured() 上的参数 size 是其右侧的 padding(40.dp) 测量到的尺寸。该匿名对象与其左侧的 padding(20.dp) 没有关联。
3、OnPlacedModifier
传统的 View 体系下在进行测量与布局时,会在测量时回调 onMeasure(),在布局时回调 onLayout()。重写 onMeasure() 有两个作用:获得测量出来的尺寸、定制自身测量过程;而重写 onLayout() 可以获取 View 的最终尺寸以及它在父容器中的相对位置,并且对于 ViewGroup 来说,还可以通过调用每个子 View 的 layout() 精准摆放这些子 View。
与这两个函数相对应的,Compose 中有两个 Modifier,分别是 OnRemeasuredModifier 与 OnPlacedModifier,这两个 Modifier 只是分别在完成测量与布局时做一个通知回调。二者相比,OnRemeasuredModifier 回调的时机更早,但 OnPlacedModifier 回调提供的信息更全面:
setContent {
Text("Compose",
Modifier
.onPlaced { layoutCoordinates ->
// 收到回调时,layout() 的布局已经结束,无法对其进行干预
println("onPlaced:${layoutCoordinates.positionInParent()}")
}
.onSizeChanged { intSize ->
// measurable.measure() 完成后会先回调这个,可以干预 layout() 的布局工作
println("onSizeChanged:(${intSize.width},${intSize.height})")
}
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
println("测量完成")
layout(placeable.width, placeable.height) {
placeable.placeRelative(10, 10)
println("子组件布局完成")
}
}
)
}
查看输出结果:
测量完成
onSizeChanged:(155,49)
onPlaced:Offset(0.0, 0.0)
子组件布局完成
Modifier.layout() 内,measurable.measure() 执行完毕意味着测量完成,此时会先回调 onSizeChanged(),然后进入 layout() 开始布局,子组件布局完成后会回调 onPlaced(),此时 layout {…} 内的父组件的布局仍在进行,所以会先输出 onPlaced() 内的回调内容,最后输出“子组件布局完成”。
再看两个回调函数提供的信息:
- onSizeChanged() 的回调参数是 IntSize,只有宽高两个属性
- onPlaced() 的回调参数是 LayoutCoordinates,除了提供尺寸的 IntSize,还提供了扩展函数 positionInParent()、positionInRoot()、positionInWindow() 分别表示组件在父容器、根组件、整个 Window 中的坐标。此外还有 windowToLocal() 与 localToWindow() 这样的坐标转换函数
通过对比我们能总结出 OnPlacedModifier 用于监听组件全局位置变化,核心作用是在所有子组件完成布局并确定最终屏幕坐标后触发回调,开发者可通过它获取组件的精确位置信息,进而实现坐标依赖的交互逻辑(如定位浮动菜单、同步动画等)。它的常用场景如下:
-
获取组件的全局坐标:
// 实时追踪 Text 组件的窗口坐标,用于与其他组件(如 Tooltip)对齐 var textPosition by remember { mutableStateOf(Offset.Zero) } Text( text = "Hello", modifier = Modifier.onPlaced { coordinates -> textPosition = coordinates.positionInWindow() } )
-
动态布局调整:
// 在父容器中根据子组件位置动态调整其他子组件的布局 Box(Modifier.onPlaced { coordinates -> val bounds = coordinates.boundsInParent() // 根据父容器内坐标调整子组件位置 })
-
触发动画:
// 在组件位置变化后触发后续动画 val animatedOffset by animateOffsetAsState(targetOffset) Box( Modifier .offset { animatedOffset } .onPlaced { coordinates -> // 根据新位置触发下一阶段动画 } )
4、LookaheadOnPlacedModifier
LookaheadOnPlacedModifier 相比于 OnPlacedModifier 会多提供 Lookahead 的信息,look ahead 可以译为“预测未来,计划未来”,总之是要表达一种前瞻或者未雨绸缪的意思。比如你要做一个编译器,在读代码的时候可能会遇到一个 “for”,但是你不能马上就认为代码要进入一个 for 循环,而是应该再向前多看几个字符,它也有可能是变量名 “forest”,这个向前看的动作就是 look ahead。
look ahead 以往是常用于算法领域中的,Compose 将它用于界面尺寸与位置的算法上,也就是 LookaheadLayout。它的特殊之处在于它会进行两次测量和布局的流程,在正式的测量和布局之前,会有一个前瞻(lookahead)测量和布局过程。在正式的测量和布局过程中,可以加一个额外的 LayoutModifier,对前瞻测量和布局的结果加以修正。
LookaheadLayout 的主要功能可能还是在页面进行状态切换的时候,可以在不同状态之间可以进行相对平滑渐进式的切换,此处暂时先不详细介绍。
LookaheadOnPlacedModifier 的作用与 OnPlacedModifier 相当,只不过前者能在 LookaheadLayout 的内部使用,并且提供一个额外信息,在正式测量与布局阶段得以回调,并提供前瞻测量与布局结果,该结果会被 LookaheadLayout 加以利用。
在写法和原理上,LookaheadOnPlacedModifier 也与 OnPlacedModifier 相似。在 Modifier 上可以调用两种 onPlaced():
Modifier.onPlaced { lookaheadScopeCoordinates -> {} }
Modifier.onPlaced { lookaheadScopeCoordinates, layoutCoordinates -> {} }
其中第一个 onPlaced() 的函数参数只有一个 LayoutCoordinates 类型的参数:
/**
* 在父 [LayoutModifier] 被放置之后、子 [LayoutModifier] 被放置之前调用 [onPlaced]。
* 这允许子 [LayoutModifier] 根据其父布局来调整自身的放置位置。
*/
@Stable
fun Modifier.onPlaced(
onPlaced: (LayoutCoordinates) -> Unit
) = this.then(
OnPlacedModifierImpl(
callback = onPlaced,
inspectorInfo = debugInspectorInfo {
name = "onPlaced"
properties["onPlaced"] = onPlaced
}
)
)
而第二个 onPlaced() 的函数参数有两个,并且该 Modifier 的扩展函数是定义在 LookaheadLayoutScope 接口中的:
@ExperimentalComposeUiApi
interface LookaheadLayoutScope {
/**
* 在父 [LayoutModifier] 被放置之后、子 [LayoutModifier] 被放置之前调用 [onPlaced]。
* 这允许子 [LayoutModifier] 根据其父布局来调整自身的放置位置。
*
* [onPlaced] 回调将以 [LookaheadLayout] 发出的 [LayoutNode] 的 [LookaheadLayoutCoordinates]
* 作为第一个参数,以及此修饰符的 [LookaheadLayoutCoordinates] 作为第二个参数被调用。通过
* [LookaheadLayoutCoordinates.localLookaheadPositionOf] 和
* [LookaheadLayoutCoordinates.localPositionOf],可以分别计算出该修饰符在
* [LookaheadLayout] 坐标系中的前瞻位置和当前位置。
*/
fun Modifier.onPlaced(
onPlaced: (
lookaheadScopeCoordinates: LookaheadLayoutCoordinates,
layoutCoordinates: LookaheadLayoutCoordinates
) -> Unit
): Modifier
}
因此这个 onPlaced() 需要在 LookaheadLayout() 中使用,因为 LookaheadLayout() 的第一个参数 content 指定接收者为 LookaheadLayoutScope,提供了调用 onPlaced() 的环境:
@Suppress("ComposableLambdaParameterPosition")
@ExperimentalComposeUiApi
@UiComposable
@Composable
fun LookaheadLayout(
content: @Composable @UiComposable LookaheadLayoutScope.() -> Unit,
modifier: Modifier = Modifier,
measurePolicy: MeasurePolicy
)
大致的使用方式如下:
LookaheadLayout(
content = { Row(Modifier.onPlaced { lookaheadScopeCoordinates, layoutCoordinates -> }) {} },
measurePolicy = // 测量逻辑
)
原理与 OnPlacedModifier 几乎一样。
OnPlacedModifier、LookaheadOnPlacedModifier 与 OnRemeasuredModifier 被归类为 LayoutAware,指对于测量与布局过程有感知,测量与布局过程会导致它们的回调函数被调用。
5、OnGloballyPositionedModifier
/**
* 一个修饰符,当内容的全局位置可能已经改变时,使用布局的最终 LayoutCoordinates 调用
* [onGloballyPositioned]。注意,它将在合成完成后、坐标最终确定时被调用。
*/
@JvmDefaultWithCompatibility
interface OnGloballyPositionedModifier : Modifier.Element {
/**
* 在测量后,使用布局最终的 LayoutCoordinates 调用本函数。注意,它是在
* 一次组合(composition)完成后,当 coordinates 最终确定后被调用。
* 在 Modifier 链中的位置对 [LayoutCoordinates] 参数或 [onGloballyPositioned]
* 的调用时机没有影响。
*/
fun onGloballyPositioned(coordinates: LayoutCoordinates)
}
GloballyPositioned 取 GPS(Global Positioning System,全球定位系统)之意,可以将 OnGloballyPositionedModifier 理解为一个全局定位的 Modifier。当 OnGloballyPositionedModifier 所在的 LayoutNodeWrapper 的尺寸或位置被刷新了,就会回调 onGloballyPositioned()。
举一个代码示例:
// OnGloballyPositionedModifier 归 Text() 对应的 InnerPlaceable 管理
Text("haha", Modifier.onGloballyPositioned { })
Text("haha",
Modifier
.onGloballyPositioned { } // 归右侧的 size() 管理
.size(40.dp)
)
Text(
"haha",
Modifier
.onGloballyPositioned { } // 归右侧的 size() 管理
.size(40.dp)
.onGloballyPositioned { } // 归右侧的 padding() 管理
.padding(20.dp)
)
onGloballyPositioned() 的参数 LayoutCoordinates 与前面的 OnPlacedModifier 与 LookaheadOnPlacedModifier 的回调函数的参数类型是一样的,它表示一个存储布局测量边界(MeasureBox)的持有者,LayoutNodeWrapper 就实现了这个接口:
internal abstract class LayoutNodeWrapper(
internal val layoutNode: LayoutNode
) : LookaheadCapablePlaceable(), Measurable, LayoutCoordinates, OwnerScope,
(Canvas) -> Unit {
...
}
而实际上,OnPlacedModifier、LookaheadOnPlacedModifier 与 OnGloballyPositionedModifier 的回调函数真正传入的就是它们所在的、管理它们的那个 LayoutNodeWrapper 对象。那为什么参数不直接规定一个 LayoutNodeWrapper 类型,而是使用父接口 LayoutCoordinates 呢?这要看一下 LayoutCoordinates 的继承关系。
LayoutCoordinates 有两个直接的子类或子接口:
- 抽象类 LayoutNodeWrapper,有两个具体子类 InnerPlaceable 和 ModifiedLayoutNode
- 子接口 LookaheadLayoutCoordinates,有一个实现类 LookaheadLayoutCoordinatesImpl
参数使用接口类型而不是接口的衍生类型,遵循了接口隔离原则(ISP)与最小暴露原则(只暴露 LayoutCoordinates 接口的 7 个核心函数隐藏了 LayoutNodeWrapper 内 20+ 细节实现函数),同时保证了框架的演进。这种设计模式在 Compose 中广泛应用(如 Modifier
/Composable
等接口),是框架保持高扩展性和稳定性的核心机制。
把接口缩小到恰好够用的范围,可以减少不小心写错代码而发生错误的机会。
当我们想得到一个区域(onGloballyPositioned() 至其右侧最近的 LayoutModifier 这段区域区域)尺寸或位置发生变化的回调时,可以使用 onGloballyPositioned()。看似与 onPlaced() 功能类似,两个接口内函数的参数都是它们内部的 Modifier 所属的 LayoutModifier(右边设置了 LayoutModifier)或者所在的 Composable 函数(没有显式设置 LayoutModifier)所对应的 LayoutNodeWrapper。
但 onGloballyPositioned() 与 onPlaced() 的区别是回调时机不同。OnPlacedModifier 是在测量和布局过程中,在 LayoutModifier 或 Composable 函数完成测量,进行摆放时回调 onPlaced(),但是它们内层的 LayoutModifier 或 Composable 函数还没有摆放,此时可以通过设置 OnPlacedModifier 来影响内层的布局摆放过程。
而 OnGloballyPositionedModifier 则是在自己所对应的 LayoutNodeWrapper 相对于窗口的位置和尺寸被更新时回调。比如 List 中的 Item 可能会有图标,在 List 滑动时,图标相对于该 Item 是没有尺寸与位置的变化的,因此 OnPlacedModifier 不会被触发,但是图标相对于整个窗口的位置是发生了变化的,因此会触发 OnGloballyPositionedModifier。
但实际使用时,OnGloballyPositionedModifier 往往会在有可能发生尺寸与位置变化时就被回调。它被触发的场景与次数会远远多于 OnPlacedModifier,也就更重量级。所以在对二者进行选择时,往往能用 OnPlacedModifier 就尽量用 OnPlacedModifier,OnPlacedModifier 不够用时才用 OnGloballyPositionedModifier。
原理上,还是两个部分,存储与取出使用。存还是在 LayoutNode 中进行:
private var onPositionedCallbacks:
MutableVector<Pair<LayoutNodeWrapper, OnGloballyPositionedModifier>>? = null
internal fun getOrCreateOnPositionedCallbacks() = onPositionedCallbacks
?: mutableVectorOf<Pair<LayoutNodeWrapper, OnGloballyPositionedModifier>>().also {
onPositionedCallbacks = it
}
override var modifier: Modifier = Modifier
set(value) {
...
val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap ->
...
toWrap.entities.addBeforeLayoutModifier(toWrap, mod)
// 将 Pair(toWrap, mod) 赋值给 onPositionedCallbacks
if (mod is OnGloballyPositionedModifier) {
getOrCreateOnPositionedCallbacks() += toWrap to mod
}
val wrapper = if (mod is LayoutModifier) {
(reuseLayoutNodeWrapper(toWrap, mod)
?: ModifiedLayoutNode(toWrap, mod)).apply {
onInitialize()
updateLookaheadScope(mLookaheadScope)
}
} else {
toWrap
}
wrapper.entities.addAfterLayoutModifier(wrapper, mod)
wrapper
}
...
}
实际上就是将 OnGloballyPositionedModifier 以 Pair 的形式赋值给 onPositionedCallbacks。
取出触发回调的代码是在 AndroidComposeView 进行测量和布局时:
override fun measureAndLayout(sendPointerUpdate: Boolean) {
trace("AndroidOwner:measureAndLayout") {
val resend = if (sendPointerUpdate) resendMotionEventOnLayout else null
val rootNodeResized = measureAndLayoutDelegate.measureAndLayout(resend)
if (rootNodeResized) {
requestLayout()
}
// 分发 OnPositioned 回调
measureAndLayoutDelegate.dispatchOnPositionedCallbacks()
}
}
dispatchOnPositionedCallbacks() 最终会调用到 OnGloballyPositionedModifier 内的接口函数:
// MeasureAndLayoutDelegate:
fun dispatchOnPositionedCallbacks(forceDispatch: Boolean = false) {
if (forceDispatch) {
onPositionedDispatcher.onRootNodePositioned(root)
}
// 向下分发
onPositionedDispatcher.dispatch()
}
// OnPositionedDispatcher:
fun dispatch() {
// sort layoutNodes so that the root is at the end and leaves are at the front
layoutNodes.sortWith(DepthComparator)
layoutNodes.forEachReversed { layoutNode ->
if (layoutNode.needsOnPositionedDispatch) {
dispatchHierarchy(layoutNode)
}
}
layoutNodes.clear()
}
private fun dispatchHierarchy(layoutNode: LayoutNode) {
layoutNode.dispatchOnPositionedCallbacks()
layoutNode.needsOnPositionedDispatch = false
layoutNode.forEachChild { child ->
dispatchHierarchy(child)
}
}
// LayoutNode:
internal fun dispatchOnPositionedCallbacks() {
if (layoutState != Idle || layoutPending || measurePending) {
return // it hasn't yet been properly positioned, so don't make a call
}
if (!isPlaced) {
return // it hasn't been placed, so don't make a call
}
onPositionedCallbacks?.forEach {
// 回调 OnGloballyPositionedModifier 的接口函数
it.second.onGloballyPositioned(it.first)
}
}
说明,只要进行测量和布局,就会触发 OnGloballyPositionedModifier 的 onGloballyPositioned() 回调。
6、ModifierLocal
ModifierLocal 属于写 SDK 或第三方库会经常用到,但是写界面很少能用到的知识。它涉及到 ModifierLocal、ModifierLocalProvider 与 ModifierLocalConsumer。
与 ThreadLocal、CompositionLocal 中的 Local 含义一样,都是它们的前缀范围内的局部变量:
- ThreadLocal 会分配给每个线程一个专属的对象,它们只在各自线程内有效,出了线程范围就无效
- CompositionLocal 是某个被指定的 Composition 或 Composable 函数内的局部变量
6.1 作用与基本使用
ModifierLocal 用于穿透 Modifier,因为多个 Modifier 之间无法共享数据。比如说我给 Modifier 的伴生对象设置两个 LayoutModifier:
Modifier
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
val widthString = placeable.width.toString()
layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
}
}
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
}
}
第二个 layout() 内是访问不到第一个 layout() 内的 widthString 变量的,因为 widthString 是第一个 layout() 的函数类型参数的函数体内的局部变量。
现在使用 modifierLocalProvider() 构造一个 ModifierLocal:
// 使用键值对的方式创建一个 ModifierLocal,key 是 ProvidableModifierLocal,value 是创建值的函数
fun <T> Modifier.modifierLocalProvider(key: ProvidableModifierLocal<T>, value: () -> T): Modifier
简单写一个例子,Modifier 达到了穿透效果:
val sharedKey = modifierLocalOf { "default value" }
Modifier
.modifierLocalProvider(sharedKey) {
"100"
}
.modifierLocalConsumer { sharedKey.current }
首先通过 modifierLocalOf() 创建一个 ProvidableModifierLocal 作为 key,在参数中指定一个生产默认值的工厂,以便在 ModifierLocal 没通过 Provider 消费时使用:
fun <T> modifierLocalOf(defaultFactory: () -> T): ProvidableModifierLocal<T> =
ProvidableModifierLocal(defaultFactory)
接下来调用 modifierLocalProvider() 时传入 key 和生产 value 的函数,注意由于使用 modifierLocalOf() 时已经指定了初始值是 String 类型,因此这里生产的 value 也应该是 String 类型。
最后通过 modifierLocalConsumer() 指定函数参数的接收者类型为 ModifierLocalReadScope,在参数的函数中可以直接通过 key 的 current 属性访问到 key 的最新值:
@Stable
@ExperimentalComposeUiApi
fun Modifier.modifierLocalConsumer(consumer: ModifierLocalReadScope.() -> Unit): Modifier {
return this.then(
ModifierLocalConsumerImpl(
consumer,
debugInspectorInfo {
name = "modifierLocalConsumer"
properties["consumer"] = consumer
}
)
)
}
这样一看,虽然名字起的是 ModifierLocal 看起来像一个 Modifier 的局部变量。但实际上,是通过键值对的方式来更新与获取 Modifier 的值的。
6.2 实际应用
利用上面讲的 modifierLocalProvider() 和 modifierLocalConsumer() 两个函数可以解决 Modifier 数据无法在多个 Modifier 间共享的问题吗?答案是不可以,比如:
Modifier
.modifierLocalProvider(sharedWidthKey) {
// 提供 Modifier 宽度数据的代码,比如宽度是 100
100
}
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
val widthString = placeable.width.toString()
layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
}
}
.modifierLocalConsumer {
// 打印 Modifier 的共享数据
println(sharedWidthKey.current)
}
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
}
}
这样做很明显达不到我们的目的。我们是想让第一个 layout() 内的变量 width 可以共享给第二个 layout(),但使用 modifierLocalProvider() 和 modifierLocalConsumer() 之后,只是实现了 modifierLocalProvider() 提供的数据共享给 modifierLocalConsumer()。
先尝试一种解决方法,就是绕过两个简便函数,直接实现它们背后的接口 ModifierLocalProvider 和 ModifierLocalConsumer:
val sharedWidthKey = modifierLocalOf { "0" }
Modifier
.then(object : LayoutModifier, ModifierLocalProvider<String> {
lateinit var widthString: String
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
): MeasureResult {
val placeable = measurable.measure(constraints)
// 测量过程中被赋值,但是无法保证获取 value 值的操作一定在赋值之后
widthString = placeable.width.toString()
return layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
}
}
override val key: ProvidableModifierLocal<String>
get() = sharedWidthKey
override val value: String
// 不能在 get() 之外初始化 value 值
get() = widthString
})
.then(object : LayoutModifier, ModifierLocalConsumer {
lateinit var sharedWidthString: String
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
): MeasureResult {
// 可以使用共享的 sharedWidthString 用于定制测量过程了
sharedWidthString
val placeable = measurable.measure(constraints)
return layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
}
}
override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) = with(scope) {
// 保存上游共享的 ModifierLocal 数据供测试过程使用,由于本函数的调用会早于
// 测量的 measure(),所以在 measure() 中使用 sharedWidthString 是安全的,
// 这种用法也是比较常规的用法
sharedWidthString = sharedWidthKey.current
}
})
这样做确实能共享 widthString,但问题是不能确保对 widthString 的访问一定在测量过程为 widthString 初始化赋值之后。一旦在 widthString 初始化之前访问它,由于 lateinit var 的影响,就会抛出异常。为了解决这个问题,可以将共享对象声明为数组,在数组中存放要共享的对象,这样下游虽然有可能会先于测量过程拿到数组,但是因为它拿到的是数组的引用,这样在测量过程进行时,为数组内的元素,也就是要共享的数据进行赋值之后,下游还是可以在要使用数据之前,去取数组内对应的元素拿到共享数据:
// 将要共享的数据放入 Array<String> 数组中
val sharedWidthKey = modifierLocalOf { arrayOf("0") }
Modifier
.then(object : LayoutModifier, ModifierLocalProvider<Array<String>> {
val widthString = arrayOf("")
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
): MeasureResult {
val placeable = measurable.measure(constraints)
// 测量过程中被赋值,但是无法保证获取 value 值的操作一定在赋值之后
widthString[0] = placeable.width.toString()
return layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
}
}
override val key: ProvidableModifierLocal<Array<String>>
get() = sharedWidthKey
override val value: Array<String>
// 不能在 get() 之外初始化 value 值
get() = widthString
})
实际开发中,还常常会同时实现 ModifierLocalProvider 和 ModifierLocalConsumer 两个接口,实现多级的连续消费的效果:
// InsetsPaddingModifier 实现了上述两个接口以及 LayoutModifier,一级处理完将数据交给下一级处理
Modifier.windowInsetsPadding(WindowInsets(4.dp, 4.dp, 4.dp, 4.dp))
.windowInsetsPadding(WindowInsets(4.dp, 6.dp, 4.dp, 6.dp))
.windowInsetsPadding(WindowInsets(4.dp, 2.dp, 4.dp, 2.dp))
6.3 原理
ModifierLocal 的作用与用法是关键,原理仅做简单了解,帮助理解作用和用法。
原理还是从存储和回调触发两个方面讲解。存储与以往大部分 Modifier 的存储类似,还是在 LayoutNode 的 modifier 属性的 set() 中:
override var modifier: Modifier = Modifier
set(value) {
...
// 迭代计算 outerWrapper
val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap ->
...
}
// 在这里处理 ModifierLocal
setModifierLocals(value)
outerWrapper.wrappedBy = parent?.innerLayoutNodeWrapper
layoutDelegate.outerWrapper = outerWrapper
}
setModifierLocals() 是管理 ModifierLocal 的提供者(Provider)与消费者(Consumer)的核心方法。它构建链式结构将 Modifier 链中的 ModifierLocalProvider 与 ModifierLocalConsumer 按顺序关联,确保 ModifierLocal 值的正确传递与更新:
// ModifierLocalProviderEntity 链表的头节点
internal val modifierLocalsHead =
ModifierLocalProviderEntity(this, SentinelModifierLocalProvider)
private fun setModifierLocals(modifier: Modifier) {
// 1.收集现有的 Modifier 的 consumers
val consumers = mutableVectorOf<ModifierLocalConsumerEntity>()
// 从 ModifierLocalProviderEntity 链表的头节点开始遍历,将所有 consumer 添加到 consumers 中
var node: ModifierLocalProviderEntity? = modifierLocalsHead
while (node != null) {
consumers.addAll(node.consumers)
node.consumers.clear()
node = node.next
}
// 2.构建新的 Modifier 链
modifierLocalsTail = modifier.foldIn(modifierLocalsHead) { lastProvider, mod ->
// 保证 ModifierLocalConsumers 在 ModifierLocalProviders 的前面,这样
// consumers 不会消费它们自己的 providers 的 value
var provider = lastProvider
...
// 添加 ModifierLocalConsumer
if (mod is ModifierLocalConsumer) {
addModifierLocalConsumer(mod, provider, consumers)
}
// 添加 ModifierLocalProvider
if (mod is ModifierLocalProvider<*>) {
provider = addModifierLocalProvider(mod, provider)
}
provider
}
// 3.清理旧的 ModifierLocalProvider 节点
// 将新链的尾节点的 next 置为 null 以断开旧链
node = modifierLocalsTail.next
modifierLocalsTail.next = null
if (isAttached) {
// 清理旧的 consumers
consumers.forEach { it.detach() }
// 清理旧的 provider
while (node != null) {
node.detach()
node = node.next
}
// 延迟附加新的 provider,延迟是为了在视图树布局完成后触发提供者的附加操作,
// 确保 ModifierLocal 值的正确初始化
forEachModifierLocalProvider { it.attachDelayed() }
}
}
接口的回调函数触发是在 AndroidComposeView 的 init 代码块中:
override val root = LayoutNode().also {
it.measurePolicy = RootMeasurePolicy
// Composed modifiers cannot be added here directly
it.modifier = Modifier
.then(semanticsModifier)
.then(rotaryInputModifier)
.then(_focusManager.modifier)
.then(keyInputModifier)
it.density = density
}
init {
...
root.attach(this)
...
}
在 LayoutNode 的 attach() 执行 forEachModifierLocalProvider():
internal fun attach(owner: Owner) {
...
forEachModifierLocalProvider { it.attach() }
...
}
尾随 lambda 内的 it 是 ModifierLocalProviderEntity,也就是调用其 attach() 封装到 lambda 中,然后在 forEachModifierLocalProvide() 中,从 ModifierLocalProviderEntity 链表的头节点开始遍历执行这个 lambda:
private inline fun forEachModifierLocalProvider(block: (ModifierLocalProviderEntity) -> Unit) {
var node: ModifierLocalProviderEntity? = modifierLocalsHead
while (node != null) {
block(node)
node = node.next
}
}
ModifierLocalProviderEntity 的 attach() 会触发所有 ModifierLocalConsumerEntity 的 attach():
fun attach() {
...
consumers.forEach { it.attach() }
}
ModifierLocalConsumerEntity 的 attach() 内会调用 notifyConsumerOfChanges() 回调 ModifierLocalConsumer 的 onModifierLocalsUpdated()
fun attach() {
isAttached = true
notifyConsumerOfChanges()
}
fun notifyConsumerOfChanges() {
if (!isAttached) return
modifierLocalsRead.clear()
val snapshotObserver = provider.layoutNode.requireOwner().snapshotObserver
snapshotObserver.observeReads(this, onReadValuesChanged) {
// modifier 是 ModifierLocalConsumerEntity 的 ModifierLocalConsumer 接口,
// 这里就回调 onModifierLocalsUpdated()
modifier.onModifierLocalsUpdated(this)
}
}