前言
传统 View 系统中,onMeasure() 是用做测量视图组件(View)及其内容的,以确定它应该占据多大的空间。我们重写它,无非有两个目的:1. 定制 View 的测量算法 2. 测量完成后,获取一个回调
其中在测量的回调中,我们可以获取组件的尺寸信息进行后续操作。
而 Jetpack Compose 中的 OnRemeasuredModifier 瞄准的就是第二点,当 OnRemeasuredModifier 所修饰的组件或者是布局节点被重新测量后,其内部的 onRemeasured() 方法就会被调用。
interface OnRemeasuredModifier : Modifier.Element {
/**
* Called after a layout's contents have been remeasured.
*/
// 在布局内容重新测量之后调用
fun onRemeasured(size: IntSize)
}
写法
只要使用 onSizeChanged() 修饰符,就行了。
Text("Hello OnRemeasuredModifier.", Modifier.onSizeChanged {
println("Text组件被测量了!")
})
但是 onSizeChanged 修饰符只有重新测量后,尺寸发生了改变,才会执行这个回调,它是一个优化的 onRemeasured()。当然初次测量时也会触发回调。
进入它的源码:
@Stable
fun Modifier.onSizeChanged(
onSizeChanged: (IntSize) -> Unit
) = this.then(
OnSizeChangedModifier( // 👈 点进去
onSizeChanged = onSizeChanged,
inspectorInfo = debugInspectorInfo {
name = "onSizeChanged"
properties["onSizeChanged"] = 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
}
}
...
}
可以看到 onSizeChanged 修饰符确实实现了 OnRemeasuredModifier 接口,并且它重写了 onRemeasured() 方法, 只有当前尺寸和上一次的尺寸不相等,才执行 onSizeChanged 回调。
当然如果你有监听每一次重新测量的需求,也可以自己实现 OnRemeasuredModifier 接口,像这样:
Modifier.then(object : OnRemeasuredModifier {
override fun onRemeasured(size: IntSize) {
// 在这里去写每一次重新测量后的逻辑
}
})
原理
了解了 OnRemeasuredModifier 的作用和写法,现在来看看它的原理。
如果不清楚 Modifier 是在哪被保存的,可以看看Modifier是怎么工作的?这篇文章。
它会被保存到 NodeChain 实例所维护的 Modifier.Node 双向链表中,然后它是怎么生效的?
它的回调函数 onRemeasured() 会在 BackwardsCompatNode 类中被使用到:
Ctrl + B 快捷键查看用法,点击设置,将查找范围设为 All Places——所有地方。
// BackwardsCompatNode.kt
override fun onRemeasured(size: IntSize) {
val element = element
if (element is OnRemeasuredModifier) {
element.onRemeasured(size)
}
}
而 BackwardsCompatNode 类的 onRemeasured() 函数会在 NodeCoordinator 类中被调用:
// NodeCoordinator.kt
fun onMeasured() {
if (hasNode(Nodes.LayoutAware)) {
Snapshot.withoutReadObservation {
visitNodes(Nodes.LayoutAware) {
it.onRemeasured(measuredSize)
}
}
}
}
NodeCoordinator 类的 onRemeasured() 函数又会在它的子类中被调用,分别是 LayoutModifierNodeCoordinator 和 InnerNodeCoordinator。
- InnerNodeCoordinator:用于测量Composable函数本身的NodeCoordinator。
- LayoutModifierNodeCoordinator:用于测量LayoutModifierNode的NodeCoordinator。
那我们分别点进去看看:
// InnerNodeCoordinator.kt
override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
layoutNode.forEachChild {
it.measurePassDelegate.measuredByParent = LayoutNode.UsageByParent.NotUsed
}
measureResult = with(layoutNode.measurePolicy) {
measure(layoutNode.childMeasurables, constraints)
}
onMeasured()
this
}
// LayoutModifierNodeCoordinator.kt
override fun measure(constraints: Constraints): Placeable {
performingMeasure(constraints) {
with(layoutModifierNode) {
measureResult = if (this is IntermediateLayoutModifierNode) {
intermediateMeasure(
wrappedNonNull,
constraints,
lookaheadDelegate!!.measureResult.let { IntSize(it.width, it.height) },
lookaheadConstraints!!
)
} else {
measure(wrappedNonNull, constraints)
}
this@LayoutModifierNodeCoordinator
}
}
onMeasured()
return this
}
其实代码很直白,就是在测量自身完成后,调用了一下 onMeasured()。
现在我问你,下面这段代码中,onRemeasured() 方法会在什么时候被调用,以及它参数的 size 指的又是谁的尺寸?
Box(
Modifier
.size(200.dp)
.padding(20.dp)
.then(object : OnRemeasuredModifier {
override fun onRemeasured(size: IntSize) {
TODO("Not yet implemented")
}
})
.padding(40.dp)
.background(Color.Green)
)
很简单,会在 padding(40.dp) 所创建的 LayoutModifierNode 完成了测量后被调用,并且尺寸也是padding(40.dp) 所创建的LayoutModifierNode 的尺寸。(尺寸大小是 160 x 160 dp)
因为 OnRemeasuredModifier 与 padding(40.dp) 创建的 LayoutModifierNode 共用一个 NodeCoordinator。