前语
前面两节中,我介绍了在Modifier中最重要的LayoutModifier
和DrawModifier
,这涉及到了Compose中组件的布局和制作能力,尤其是在Modifier初始化过程中,创立的NodeChain
,这个对理解Modifier摆放顺序的时分会有很大的帮助,从这一节开端,将会介绍一些常用的Modifier底层完结原理,比方本节的ParentDataModifier
。
1 ParentDataModifier介绍
官方的解释:
用于供给数据给到父容器,在父容器丈量和摆放的过程中能够拿到这些数据。
/**
* A [Modifier] that provides data to the parent [Layout]. This can be read from within the
* the [Layout] during measurement and positioning, via [IntrinsicMeasurable.parentData].
* The parent data is commonly used to inform the parent how the child [Layout] should be measured
* and positioned.
*/
@JvmDefaultWithCompatibility
interface ParentDataModifier : Modifier.Element {
/**
* Provides a parentData, given the [parentData] already provided through the modifier's chain.
*/
fun Density.modifyParentData(parentData: Any?): Any?
}
所以从命名来看,其实便是用来供给父容器的布局信息的,咱们其实在之前现已用到过相关的Modifier,例如align
。
@Composable
fun ModifierParentData() {
Box(modifier = Modifier.size(100.dp)) {
Text(text = "文案", Modifier.align(Alignment.Center))
}
}
它详细的效果便是让Text
组件在Box
居中展示,看下源码:
@Stable
override fun Modifier.align(alignment: Alignment) = this.then(
BoxChildData(
alignment = alignment,
matchParentSize = false,
inspectorInfo = debugInspectorInfo {
name = "align"
value = alignment
}
)
)
private class BoxChildData(
var alignment: Alignment,
var matchParentSize: Boolean = false,
inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo
) : ParentDataModifier, InspectorValueInfo(inspectorInfo) {
override fun Density.modifyParentData(parentData: Any?) = this@BoxChildData
// ......
}
从源码看,align
会创立BoxChildData
与调用者融合,而BoxChildData
便是承继自ParentDataModifier
。在modifyParentData
函数中,是将自己返回了,那么BoxChildData
其实便是外层父组件会拿到的parentData
。
2 ParentDataModifier的运用
像从前咱们运用LayoutModifier
或许DrawModifier
,咱们能够经过自界说的方法来影响体系的丈量制作过程,例如:
@Composable
fun ModifierParentData() {
Box(
Modifier
.size(100.dp)
.drawWithContent {
drawCircle(Color.Red)
drawContent()
})
}
我只需调用了drawWithContent
,那么体系在制作的时分,就会筛选拿到Node.Draw
类型的节点履行其内部的draw
函数,哪怕我就创立了一个匿名内部类,都能够履行其自界说制作操作。
@Composable
fun ModifierParentData() {
Box(
Modifier
.size(100.dp)
.drawWithContent {
drawCircle(Color.Red)
drawContent()
}.then(object : DrawModifier{
override fun ContentDrawScope.draw() {
drawCircle(Color.Red)
drawContent()
}
}))
}
可是,ParentDataModifier
能够这么做吗?
@Composable
fun ModifierParentData() {
Box(
Modifier
.size(100.dp)
.then(object : DrawModifier {
override fun ContentDrawScope.draw() {
drawCircle(Color.Red)
drawContent()
}
})
.then(
object : ParentDataModifier {
override fun Density.modifyParentData(parentData: Any?): Any? {
return null
}
}
))
}
显然不能够,因为自界说的ParentDataModifier
并不能影响现阶段Compose的丈量布局流程,因为Box
外层的父布局不认这个Modifier。
2.1 怎么运用自界说ParentDataModifier
例如,咱们在Box
中运用align
函数的时分,经过源码咱们能够看到,在Box
供给的BoxScope
效果域下,会显示声明一个align
函数。BoxScope
对应的完结类为BoxScopeInstance
,会完结align
函数,做Modifier详细完结类的创立。
/**
* A BoxScope provides a scope for the children of [Box] and [BoxWithConstraints].
*/
@LayoutScopeMarker
@Immutable
interface BoxScope {
/**
* Pull the content element to a specific [Alignment] within the [Box]. This alignment will
* have priority over the [Box]'s `alignment` parameter.
*/
@Stable
fun Modifier.align(alignment: Alignment): Modifier
// ......
}
其实不止是Box
,恣意容器类型的组件,假如要运用ParentDataModifier
,那么都需求在其效果域内声明对应的函数,并完结完结。
所以:咱们自界说的ParentDataModifier
,即单独完结匿名内部类的这种方法,是无效的。所以在运用官方供给的组件的时分,不能运用自界说ParentDataModifier
,只能运用它界说好的Modifier扩展函数。 那么ParentDataModifier
是什么场景下会运用呢?在自界说布局的时分会运用。
@Composable
fun MyContainer(modifier: Modifier = Modifier,content: @Composable MyContainerScope.() -> Unit) {
Layout(content = {content}, modifier) { measurables, constaints ->
measurables.forEach {
val value = it.parentData as? String
}
layout(100,100){
}
}
}
fun MeasureScope.measure(
measurables: List<Measurable>,
constraints: Constraints
): MeasureResult
例如在自界说布局容器时,一般都会在底层运用Layout
函数,在丈量的时分会拿到一组Measurable
数据,这些数据便是子组件丈量之后统一给到父容器。经过遍历measurables
数组,能够拿到单一的Measurable
,能够经过这个详细的对象拿到对应的ParentData
。
可是拿到的条件是,子组件会设置这个Modifier特点,例如界说了getStringData
扩展函数,这个是在MyContainerScope
效果域下才会运用到,避免API被污染,避免开发者在其他效果域下随意调用
@LayoutScopeMarker
@Immutable // 削减不必要的重组
interface MyContainerScope {
@Stable
fun Modifier.getStringData(): Modifier
}
internal object MyContainerScopeInstance : MyContainerScope {
@Stable
override fun Modifier.getStringData(): Modifier {
return this.then(object : ParentDataModifier {
override fun Density.modifyParentData(parentData: Any?): Any? {
return "MyContainerScope"
}
})
}
}
子组件运用如下所示,当Text
文本设置了getStringData
之后,父容器会接收到子组件的配置信息。
MyContainer{
Text(text = "",Modifier.getStringData())
}
2.2 ParentDataModifier留意事项
假定现在我这么运用,我从头供给了一个扩展函数weightAgain
,
@Stable
override fun Modifier.weightAgain(weight: Float): Modifier {
return this.then(object : ParentDataModifier {
override fun Density.modifyParentData(parentData: Any?): Any? {
return weight
}
})
}
在运用的时分接连调用了2次,可是传值是不一样的。
MyContainer {
Text(text = "",
Modifier
.weightAgain(1f)
.weightAgain(2f))
}
前面我在讲Modifier初始化的时分,同步阶段会从右向左更新,假如是依照Modifier中界说的,将传入的值作为modifyParentData
的返回值,那么终究同步完结之后,innerCoordinator
内部的weightAgain
便是1f。
@Stable
override fun Modifier.weightAgain(weight: Float): Modifier {
return this.then(object : ParentDataModifier {
override fun Density.modifyParentData(parentData: Any?): Any? {
return (parentData as? Float)?.plus(weight)
}
})
}
当然也能够组合,在modifyParentData(parentData: Any?)
函数中的参数,便是上一个调用weightAgain
传入的值,可是一般情况下没有这么用的,能够可是没有必要。
可是下面的这个场景,是非常或许遇到的:
MyContainer {
Text(text = "",
Modifier
.getStringData()
.weightAgain(2f))
}
两个不同的ParentDataModifier
组合在一起,那么在MyContainer
中取值ParentData
的时分,该怎么取?取Float
仍是String
?
@LayoutScopeMarker
@Immutable
interface MyContainerScope {
@Stable
fun Modifier.getStringData(value: String): Modifier
@Stable
fun Modifier.weightAgain(weight: Float): Modifier
}
class MultiData(var value: String = "", var weight: Float = 0f)
internal object MyContainerScopeInstance : MyContainerScope {
@Stable
override fun Modifier.getStringData(value: String): Modifier {
return this.then(object : ParentDataModifier {
override fun Density.modifyParentData(parentData: Any?): Any? {
return (parentData as? MultiData ?: MultiData()).also {
it.value = value
}
}
})
}
@Stable
override fun Modifier.weightAgain(weight: Float): Modifier {
return this.then(object : ParentDataModifier {
override fun Density.modifyParentData(parentData: Any?): Any? {
return (parentData as? MultiData ?: MultiData()).also {
it.weight = weight
}
}
})
}
}
这个情况下,需求经过实体数据类,来封装多参数的数据,在履行modifyParentData
函数的时分,取出对应的MultiData
,对其参数赋值。
3 ParentDataModifier原理
经过前面我对于ParentDataModifier
运用的介绍,现已知道了子组件在设置的Parentdata会在父容器Layout
的时分去获取,所以咱们看下在获取parentData
的时分是怎么拿到的?
// NodeCoordinator.kt
override val parentData: Any?
get() {
var data: Any? = null
val thisNode = tail
if (layoutNode.nodes.has(Nodes.ParentData)) {
with(layoutNode.density) {
layoutNode.nodes.tailToHead {
if (it === thisNode) return@tailToHead
if (it.isKind(Nodes.ParentData) && it is ParentDataModifierNode) {
data = with(it) { modifyParentData(data) }
}
}
}
}
return data
}
首要创立一个data
数据,默认为null;然后判断NodeChain
中是否存在Nodes.ParentData
类型的节点,假如不存在,那么直接返回null;
假如NodeChain
存在Nodes.ParentData
类型的节点,以上面介绍的例子:
graph LR
Head --> getStringData --> weightAgain --> Tail
会从tail遍历到head,假如当前节点是ParentDataModifierNode
,那么就会履行其modifyParentData
函数,留意这里是会给data
从头赋值,并且会把上次的data
作为参数传递到下一个遍历的ParentDataModifierNode
中的modifyParentData
函数。这也便是为什么在前面介绍的时分,假如两个接连调用的ParentDataModifier
参数会被掩盖的问题。
遍历完结之后,将data返回,那么父容器就会拿到对应的子组件的数据信息。