作用
OnPlacedModifier 和 OnRemeasuredModifier 属于兄弟 Modifier。OnRemeasuredModifier 可以让我们得到测量结束后会调用的回调函数,而 OnPlacedModifier 的作用也是得到一个回调,只不过是摆放后的回调。
那这两个回调有什么区别吗?
第一个区别:回调函数的调用时机不同。在 Compose 中布局过程可以拆分为测量(measure)和摆放(placement)两个阶段,如果你想要影响到测量和摆放过程,你就只能使用 OnRemeasuredModifier,它会在测量结束后被调用,而不能使用 OnPlacedModifier,等到 OnPlacedModifier 的回调被调用时,整个布局过程已经结束了,你是影响不到测量和摆放过程。
第二个区别:OnPlacedModifier 的回调中包含更多的信息,其中主要是位置信息。
我们进入 OnPlacedModifier 对应的修饰符函数 Modifier.onPlaced():
@Stable
fun Modifier.onPlaced(
onPlaced: (LayoutCoordinates) -> Unit
) = this then OnPlacedElement(onPlaced)
可以看到这个函数类型的参数 onPlaced,它也有一个参数,类型是 LayoutCoordinates,它的翻译是布局坐标,点进去简单看看:
// LayoutCoordinates.kt
interface LayoutCoordinates {
// 组件的尺寸,包含宽度和高度
val size: IntSize
// 组件提供的对齐线信息,用于对齐相关操作
val providedAlignmentLines: Set<AlignmentLine>
// 将窗口坐标系中的点转换为组件本地坐标系
fun windowToLocal(relativeToWindow: Offset): Offset
// 将组件本地坐标系中的点转换为窗口坐标系
fun localToWindow(relativeToLocal: Offset): Offset
}
// 获取组件在窗口坐标系中的位置
// 通过将本地原点(0,0)转换到窗口坐标系实现
fun LayoutCoordinates.positionInWindow(): Offset = localToWindow(Offset.Zero)
// 获取组件在父组件坐标系中的位置
fun LayoutCoordinates.positionInParent(): Offset =
parentLayoutCoordinates?.localPositionOf(this, Offset.Zero) ?: Offset.Zero
使用原则:需要尺寸信息,并且要更早得到,就使用 OnRemeasuredModifier,如果你需要位置等信息,就选择 OnPlacedModifier。
写法
使用提供的 Modifier.onPlaced() 修饰符函数就可以了。
比如:
Box(Modifier
.size(100.dp)
.padding(6.dp)
.background(Color.Green)
.onPlaced { coordinates ->
val windowPosition = coordinates.positionInWindow()
println("在屏幕的坐标是 $windowPosition")
}
)
原理
它的原理和 OnRemeasuredModifier 的原理是差不多的。
在每一层的 NodeCoordinator 协调器的测量完成之后,会被更外层的 NodeCoordinator 协调器调用它的 onPlaced() 函数:
/**
* 在组件完成布局放置后调用的函数
* 此函数会遍历所有标记为LayoutAware的节点,并通知它们布局已完成
*/
fun onPlaced() {
// 访问所有被标记为LayoutAware的节点
visitNodes(Nodes.LayoutAware) {
// 对每个节点调用onPlaced方法,传入当前布局上下文
it.onPlaced(this)
}
}
LayoutAware的节点对应的 Modifier 有三个,分别是OnRemeasuredModifier、OnPlacedModifier、LookaheadOnPlacedModifier。
点开这个 onPlaced() 方法:
interface LayoutAwareModifierNode : DelegatableNode {
fun onPlaced(coordinates: LayoutCoordinates) {}
fun onRemeasured(size: IntSize) {}
}
再进入这个方法的实现处,来到 BackwardsCompatNode 类中:
/**
* 当组件完成布局放置后被调用的方法
*
* @param coordinates 包含组件布局位置和尺寸信息的LayoutCoordinates对象
*/
override fun onPlaced(coordinates: LayoutCoordinates) {
// 保存最近一次的布局坐标信息,以便后续可能的使用
lastOnPlacedCoordinates = coordinates
// 获取关联的元素(通常是修饰符)
val element = element
// 检查元素是否实现了OnPlacedModifier接口
if (element is OnPlacedModifier) {
// 如果是,则调用该元素的onPlaced方法,传递布局坐标信息
element.onPlaced(coordinates)
}
}
这里面的 onPlaced() 方法内部,就是我们传入的摆放回调逻辑。
总结
OnPlacedModifier 的作用是让我们获取子Composable函数或者是子LayoutModifierNode 被 父Composable函数或者是父的LayoutModifierNode去摆放后的回调,我们可以在里面获取最终的尺寸信息、对齐线信息等。
OnPlacedModifier 获取的回调和 OnRemeasuredModifier 获取的回调的区别:前者提供的信息更多,后者被调用的时机更早。