Compose 实践与探索十一 —— ParentDataModifier

Before 阶段的 Modifier 还剩 ParentDataModifier 没讲,它是测量与布局过程中起到辅助作用的 Modifier。接下来我们会从它的作用、使用与原理三个方面来讲解这个 Modifier。

1、作用

我们会通过讲解 ParentDataModifier 接口的两个实现类,来说明 ParentDataModifier 的作用。

1.1 weight()

我们先来看一段代码:

@Composable
fun ParentDataModifierSample() {
    Row {
        Box(Modifier.size(40.dp).background(Color.Blue).weight(1f))
        Box(Modifier.size(40.dp).background(Color.Red))
        Box(Modifier.size(40.dp).background(Color.Green))
    }
}

当没有给蓝色 Box 添加 weight() 之前,红绿蓝三色 Box 是相同大小,都是 40dp。在给蓝色 Box 添加 weight() 之后,它会占满这一行剩余的空间:

在这里插入图片描述

说明 Compose 中的 weight() 与原生 View 体系中的 layout_weight 属性的作用是一样的。那么现在来思考,weight() 内部是使用哪一个 Modifier 实现的?看起来像是 LayoutModifier,但点进 weight() 的源码,你会先看到它是 RowScope 接口内定义的抽象函数:

@LayoutScopeMarker
@Immutable
interface RowScope {
    /**
     * 根据元素的[weight]相对于[Row]中其他带权重的兄弟元素的比例调整元素的宽度。父元素将在测量
     * 无权重子元素后,将水平剩余空间分配给子元素,比例由该权重决定。
     * 当[fill]为 true 时,元素将被强制占用分配给它的整个宽度。否则,允许元素更小 - 这将导致[Row]更小,
     * 因为未使用的分配宽度将不会重新分配给其他兄弟元素。
     * @param weight 给予该元素的比例宽度,相对于所有带权重兄弟元素的总和。必须为正数。
     * @param fill 当为 true 时,元素将占用分配的整个宽度。
     */
    @Stable
    fun Modifier.weight(
        /*@FloatRange(from = 0.0, fromInclusive = false)*/
        weight: Float,
        fill: Boolean = true
    ): Modifier
}

找到 RowScope 唯一的实现类 RowScopeInstance,发现 weight() 内部使用的是 LayoutWeightImpl:

internal object RowScopeInstance : RowScope {
    @Stable
    override fun Modifier.weight(weight: Float, fill: Boolean): Modifier {
        require(weight > 0.0) { "invalid weight $weight; must be greater than zero" }
        return this.then(
            LayoutWeightImpl(
                weight = weight,
                fill = fill,
                inspectorInfo = debugInspectorInfo {
                    name = "weight"
                    value = weight
                    properties["weight"] = weight
                    properties["fill"] = fill
                }
            )
        )
    }
}

而 LayoutWeightImpl 实现的是 ParentDataModifier,不是 LayoutModifier:

internal class LayoutWeightImpl(
    val weight: Float,
    val fill: Boolean,
    inspectorInfo: InspectorInfo.() -> Unit
) : ParentDataModifier, InspectorValueInfo(inspectorInfo)

并且 ParentDataModifier 不是 LayoutModifier 的子接口,而是一个直接继承自 Modifier.Element 的独立接口:

/**
* 一个修饰符,向父布局提供数据。这可以在测量和定位期间通过 IntrinsicMeasurable.parentData
* 从布局内部读取。Parent data 通常用于告知父级子布局应如何测量和定位。
*/
@JvmDefaultWithCompatibility
interface ParentDataModifier : Modifier.Element {
    /**
     * 通过 Modifier 链提供的 [parentData] 向外提供 parentData
     */
    fun Density.modifyParentData(parentData: Any?): Any?
}

通过 ParentDataModifier 接口上的注释,你应该了解了 ParentDataModifier 的用途了,就是向父布局提供子组件的数据,以便让父布局知道子组件应该如何测量和布局。比如我们举例的 weight(),虽然是设置在 Box 上的,但这个数据是会被父布局的 Row 获取到,从而让 Row 知道应该如何对其内部的 Box 进行测量与布局。所以这个 ParentData 可以理解为给父布局使用的数据。

ParentDataModifier 并不是像 LayoutModifier 那样可以直接影响其所修饰的组件的测量与布局的 Modifier。比如例子中的 Box 使用 size(40.dp),那么 LayoutModifier 就会让该 Box 的首选尺寸是 40dp。而 ParentDataModifier 则是将其修饰的组件的诉求告知给它的父组件,父组件收集其内部所有子组件的诉求后,根据相关规则来处理子组件的诉求。因此 ParentDataModifier 是一种间接影响组件测量与布局的 Modifier。

那为什么不使用 LayoutModifier 来做这件事?因为做不了,或者说,很难做。就以我们举得 Row 中有三个 Box 的例子,假如每个 Box 都设置了各自的 weight(),那么 LayoutModifier 这种专注于某个单一组件的 Modifier,就势必要获取该组件的所有兄弟组件的 Modifier 内设置的 weight() 值,这样才能计算出总的 weight 值,再计算出自己的 weight 所占的比例便于分配宽度值。如果这样做的话,LayoutModifier 就有两个明显越权的地方:

  1. 额外获取了兄弟组件的 weight 值
  2. 获取了父组件的宽度

LayoutModifier 不再单一的只针对它所修饰的组件,还要掌握所有兄弟组件的 weight 使得 LayoutModifier 的功能混乱,并且这样实现也很麻烦。而像 Compose 设计的那样,让父组件来获取 ParentData 信息并进行计算测量,不仅可以在职权内获取到自己的内部宽度属性,也可以方便地计算出每个子组件的 weight(不用每个 LayoutModifier 内都保存一份 weight 数据,只需保存一份数据即可算出所有子组件的 weight),这就是不用 LayoutModifier 而使用 ParentDataModifier 的原因。

实际上,weight 属性交由父级组件计算其实是一种通用设计,原生 View 体系下的权重 layout_weight 实际上也是给父布局使用的。包括 layout_width 与 layout_height 这些 layout 开头的属性都是用于父布局测量和布局使用的。

1.2 layoutId()

再看第二个例子,Modifier.layoutId():

@Stable
fun Modifier.layoutId(layoutId: Any) = this.then(
    LayoutId(
        layoutId = layoutId,
        inspectorInfo = debugInspectorInfo {
            name = "layoutId"
            value = layoutId
        }
    )
)

使用它可以为组件指定一个标签(tag),便于父组件在测量和布局时针对该组件做一些特殊处理。比如自定义一个 CustomLayout,在内部通过 Layout() 进行测量过程时,可以通过 Measurable 获取到这个 layoutId,然后根据需求做出相应的处理:

@Composable
fun CustomLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
    Layout(content, modifier) { measurables, constraints ->
        // 对每个组件进行测量                       
        measurables.forEach { // it:Measurable
            // 获取 Measurable 的 layoutId 做出对应的测量
            when (it.layoutId) {
                "big" -> it.measure(constraints.xxx)
                "small" -> it.measure(constraints.yyy)
                else -> it.measure(constraints)
            }
        }
        // 布局的模拟代码                       
        layout(100, 100) {
            ...
        }
    }
}

这里为了让代码更直观一些,就对 layoutId 的值做了硬编码,实际项目中一定不会直接像 “big”、“small” 这样写的,一旦写错单词错误会很难排查。

使用时在 CustomLayout 的子组件的 modifier 上调用 layoutId():

CustomLayout(Modifier.size(40.dp)) {
    Text("Jetpack", Modifier.layoutId("big"))
    Text("Compose", Modifier.layoutId("small"))
}

这样父布局 CustomLayout 在测量时,就会根据 Text 指定的 layoutId 内容做出不同的测量动作。这个例子也能看出,layoutId 背后的 ParentDataModifier 对父布局的测量起到了辅助作用。

综上,我们通过 weight() 与 layoutId() 两个例子,说明了 ParentDataModifier 是一个辅助测量与布局过程的 Modifier。

2、用法

本节介绍如果想自定义一个 ParentDataModifier 应该怎么写。

2.1 自定义 ParentDataModifier

根据以往的经验,最直接的使用方式是在 Modifier 链中用 then() 连接一个 ParentDataModifier 的匿名对象:

Row {
    Box(
        Modifier
            .size(40.dp)
            .background(Color.Red)
            .then(object : ParentDataModifier {
                override fun Density.modifyParentData(parentData: Any?): Any? {
                    TODO("Not yet implemented")
                }
            })
    )
}

但是这样的话,在实现 modifyParentData() 的具体内容时,你需要去查阅 Box 的父组件 Row 的内部都用到了哪些 parentData,这是一件很麻烦的事情。因此,这种方式不可行。

参考现成的 weight() 的用法:

internal object RowScopeInstance : RowScope {
    @Stable
    override fun Modifier.weight(weight: Float, fill: Boolean): Modifier {
        require(weight > 0.0) { "invalid weight $weight; must be greater than zero" }
        return this.then(
            LayoutWeightImpl(
                weight = weight,
                fill = fill,
                inspectorInfo = debugInspectorInfo {
                    name = "weight"
                    value = weight
                    properties["weight"] = weight
                    properties["fill"] = fill
                }
            )
        )
    }
}

weight() 是父组件 Row 的作用域 RowScope 提供给子组件使用的 Modifier,因此子组件在使用 ParentDataModifier 时,直接调用 weight() 即可,无需写成 then() + 匿名对象的形式。因为父组件能供你使用的 ParentDataModifier 已经通过weight() 这样的便捷函数给你了,如果你想增加某种父组件没提供的 ParentDataModifier 功能,只通过写一个匿名子类用 then() 连接上是没用的,因为父组件内部没有对于该 ParentDataModifier 实现类的相应处理,因此相当于你白写了。

上面说的是,如何使用父组件提供的已经实现好的 ParentDataModifier 的功能。假如,你想自定义一个提供 ParentDataModifier 功能的父组件,应该做如下三件事:

  1. 组件内部要使用 Layout(),因为只有使用它才能通过它的第三个参数 MeasurePolicy 接口内 measure() 的参数获得 measurables: List<Measurable> 参数,进而遍历 measurables 进行测量
  2. 在 Layout() 内遍历 measurables 时,去拿到并使用每一个子组件提供的 ParentDataModifier 信息
  3. 在组件内写一个提供 ParentDataModifier 的函数,类似 weight() 与 layoutId() 那样,方便开发者使用

基于以上三点,可以写个例子:

@Composable
fun CustomLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
    Layout(content, modifier) { measurables, constraints ->
        measurables.forEach {
            // 这个强转的类型需要根据你的业务需求做相应的转换,这里只是举例转成 String
            val data = it.parentData as? String
            ... // 自定义布局的后续代码
        }
        layout(100, 100) {
            // 布局代码...
        }
    }
}

通过 forEach 遍历 measurables,Measurable 的 parentData 就是子组件的 ParentDataModifier 数据,类型是 Any?,因此在实现具体的自定义 Composable 函数时,需要将它强转为你所需要的类型,比如上面的 String。转换时需要注意使用 as? 而不是 as,因为并不是所有的组件都会设置 ParentDataModifier,对于这样的组件,它的 parentData 就为空,如果使用 as 强转会引发 NPE。

以上就完成了前两点,最后一点可以直接实现一个 ParentDataModifier 填入 then() 中即可:

fun Modifier.stringData() = then(object : ParentDataModifier {
    override fun Density.modifyParentData(parentData: Any?): Any? {
        // 带上右侧 ParentDataModifier 的 parentData 数据
        return "Compose: $parentData"
    }
})

modifyParentData() 的参数 parentData 是 Modifier 链上位于当前 Modifier 右侧的 ParentDataModifier 提供的 parentData 数据,之所以出现在参数上,是为了不遗弃右侧 ParentDataModifier 的数据,融合在一起使用的。当然,它提供给你的初衷是希望你不要遗弃数据,但你也需要结合实际应用场景,比如对于 weight() 这样的修饰符,如果连用了两个:

Modifier.weight(1f).weight(2f)

这种情况你对它做融合就没有任何意义,直接用后处理的 1f 覆盖先处理的 2f 即可:

// weight() 内 LayoutWeightImpl 的实现
override fun Density.modifyParentData(parentData: Any?) =
    ((parentData as? RowColumnParentData) ?: RowColumnParentData()).also {
        it.weight = weight
        it.fill = fill
    }

由于我们还没有到自定义布局的部分,所以现在举的例子只是为了说明 ParentDataModifier 的使用方式,完整的使用示例,会在后续介绍自定义布局时展示。

2.2 多 ParentDataModifier 的处理

以上我们说的是子组件只使用了一种 ParentDataModifier 的情况,假如像下面这样,子组件使用了一个以上的 ParentDataModifier:

setContent {
    CustomLayout(Modifier.size(40.dp)) {
        Text("Jetpack",
             Modifier
                 .weightData(1f)
                 .bigData(true))
    }
}

fun Modifier.bigData(big: Boolean) = then(object : ParentDataModifier {
    override fun Density.modifyParentData(parentData: Any?): Any? {
        return big
    }
})

fun Modifier.weightData(weight: Float) = then(object : ParentDataModifier {
    override fun Density.modifyParentData(parentData: Any?): Any? {
        return weight
    }
})

那么 CustomLayout 内获取 parentData 数据的方式就需要重新考量:

@Composable
fun CustomLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
    Layout(content, modifier) { measurables, constraints ->
        measurables.forEach {
            val data = it.parentData as? Float
            ... // 自定义布局的后续代码
        }
        layout(100, 100) {
            // 布局代码...
        }
    }
}

it.parentData 只能拿到 Modifier 链靠左侧的那个 ParentDataModifier 提供的 parentData 数据,也就是上例中 weightData() 的 Float 数据,但是没法拿到右侧 bigData() 的 Boolean 数据。这时候需要创建一个综合数据类承载这两个维度的 ParentDataModifier 的数据:

class LayoutData(var weight: Float = 0f, var big: Boolean = false)

bigData() 与 weightData() 在实现 modifyParentData() 时,结果也要融合到 LayoutData 中:

fun Modifier.bigData(big: Boolean) = then(object : ParentDataModifier {
    override fun Density.modifyParentData(parentData: Any?): Any? {
        // 如果 parentData 为空说明 bigData() 在最右侧,就创建 LayoutData 填入 big 属性,
        // 否则就在右侧传入的已有的 LayoutData 中添加 big 属性
        return if (parentData == null) {
            LayoutData(big = big)
        } else {
            (parentData as LayoutData).apply { this.big = big }
        }
    }
})

fun Modifier.weightData(weight: Float) = then(object : ParentDataModifier {
    override fun Density.modifyParentData(parentData: Any?): Any? {
        // 如果 parentData 为空就创建 LayoutData,否则就沿用参数传入的 LayoutData,添加 weight 属性
        return ((parentData as? LayoutData) ?: LayoutData()).also { it.weight = weight }
    }
})

CustomLayout 中获取到的 parentData 就可以转成 LayoutData,再分开对 big 与 weight 属性进行处理:

@Composable
fun CustomLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
    Layout(content, modifier) { measurables, constraints ->
        measurables.forEach {
            val data = it.parentData as? LayoutData
            val big = data.big
            val weight = data.weight
            ... // 自定义布局的后续代码
        }
        layout(100, 100) {
            // 布局代码...
        }
    }
}

2.3 解决 API 污染问题

上一小节创建出的 bigData() 与 weightData() 是给 CustomLayout 这个组件使用的,这两个函数只有在 CustomLayout() 的内部被调用才有意义,因此它不应该“随处可用”,只能在 CustomLayout 的环境中使用。

但按照当前代码,在 CustomLayout 之外,设置 Modifier 时会出现 bigData() 与 weightData() 的代码提示,并且可以在外部使用:

在这里插入图片描述

这就产生了 API 污染的问题。解决问题的方案可以参考 Row 中的 weight(),该函数是做了抗污染的。

首先,weight() 是被定义在 RowScope 接口中的:

@LayoutScopeMarker
@Immutable
interface RowScope {
    @Stable
    fun Modifier.weight(
        weight: Float,
        fill: Boolean = true
    ): Modifier
}

这样一来,就只有 RowScope 的实现类对象,或者在 RowScope 的环境下才能调用 weight()。而 Row 这个 Composable 函数的最后一个参数 content 指定了函数的接收者类型为 RowScope,因此可以在 Row 的尾随 lambda 表达式中使用 weight():

@Composable
inline fun Row(
    modifier: Modifier = Modifier,
    horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
    verticalAlignment: Alignment.Vertical = Alignment.Top,
    content: @Composable RowScope.() -> Unit
)

我们可以效仿上面的做法应用到 CustomLayout 上。首先创建一个 CustomLayoutScope 接口:

@LayoutScopeMarker
@Immutable
interface CustomLayoutScope {
    fun Modifier.bigData(big: Boolean): Modifier
    fun Modifier.weightData(weight: Float): Modifier
}

@LayoutScopeMarker 注解通常有两个作用:

  1. 限制 API 的可见性,被标记的接口或类的扩展函数与成员函数只在特定作用域内可见并调用
  2. DSL 作用域隔离,被标记的函数只能在特定代码块内访问

我们这里用到的是第一种,只能在直接的特定作用域内使用,在作用域之外不可见,在作用域的间接(嵌套的)内部不可调用。

@Immutable 注解可以减少重组过程中的不必要重组,一般用在接口上面。

然后实现 CustomLayoutScope 接口,把两个接口函数的具体实现拿进来:

// 使用 internal 限制 CustomLayoutScopeInstance 只在当前模块内获取
internal object CustomLayoutScopeInstance : CustomLayoutScope {
    override fun Modifier.bigData(big: Boolean) = then(object : ParentDataModifier {
        override fun Density.modifyParentData(parentData: Any?): Any? {
            // 如果 parentData 说明 bigData() 在最右侧,就创建 LayoutData 填入 big 属性,
            // 否则就在右侧传入的现有的 LayoutData 中添加 big 属性
            return if (parentData == null) {
                LayoutData(big = big)
            } else {
                (parentData as LayoutData).apply { this.big = big }
            }
        }
    })

    override fun Modifier.weightData(weight: Float) = then(object : ParentDataModifier {
        override fun Density.modifyParentData(parentData: Any?): Any? {
            // 如果 parentData 为空就创建 LayoutData,否则就沿用参数传入的 LayoutData,添加 weight 属性
            return ((parentData as? LayoutData) ?: LayoutData()).also { it.weight = weight }
        }
    })
}

最后在 CustomLayout() 中给它的 content 参数加上 CustomLayoutScope 类型的接收者:

@Composable
fun CustomLayout(
    modifier: Modifier = Modifier,
    content: @Composable CustomLayoutScope.() -> Unit
) {
    // Layout() 的第一个参数要的是 () -> Unit,因此要修改为下面的形式
    Layout({ CustomLayoutScopeInstance.content() }, modifier) { measurables, constraints ->
        measurables.forEach {
            val data = it.parentData as? LayoutData
            ...
        }
        layout(100, 100) {
            // 布局代码...
        }
    }
}

这样一来,就只能在 CustomLayout 的 content 的直接内部使用 CustomLayoutScope 内的函数了:

setContent {
    // Cannot access 'CustomLayoutScopeInstance': it is internal in 
    // 'com.jetpack.compose.scope'
    Modifier.weightData(1f)
    CustomLayout {
        // 这个位置可用
        Text("Jetpack", Modifier.weightData(1f))
        Box(
            Modifier
            	.size(20.dp)
            	.background(Color.Red)) {
            // 'fun Modifier.weightData(weight: Float): Modifier' can't be called in this 
            // context by implicit receiver. Use the explicit one if necessary
            Text("Compose", Modifier.weightData(2f))
        }
    }
}

上面演示了三处使用 weightData() 的代码,只有第二处是可以使用的:

  1. 示例代码在 CustomLayoutScopeInstance 所定义的模块之外,由于 CustomLayoutScopeInstance 是 internal 的,模块之外访问不到它,也就无法进一步使用它的 weightData() 了。实际上不能在 CustomLayoutScope 之外使用其内部的接口函数,是通过限定 CustomLayoutScope 的实现类的可见性(比如 RowScope 与 CustomLayoutScope 都是限定其在模块内可见)导致使用者拿不到 CustomLayoutScope 的实现类对象实现编译报错的
  2. 可以正常使用,因为 weightData() 是 CustomLayoutScope 的接口函数,且 CustomLayout 的尾随 lambda 函数提供了 CustomLayoutScope 这个接收者,因此在尾随 lambda 的直接内部可以使用 weightData()
  3. 在 CustomLayout 内又嵌套了一个 Box,在 Box 的直接内部、CustomLayout 的间接内部不能使用 weightData(),CustomLayoutScope 接口上用 @LayoutScopeMarker 做了限制,使得只能在 CustomLayout 的直接内部使用,不能穿透到 Box 这样的嵌套的内部。如果没加,就可以穿透到嵌套的内部

当然,你也可以直接用 object 来做这个 Scope 省去实现接口的麻烦:

@LayoutScopeMarker
object CustomLayoutScope {
    fun Modifier.bigData(big: Boolean) = then(object : ParentDataModifier {
        override fun Density.modifyParentData(parentData: Any?): Any? {
            // 如果 parentData 说明 bigData() 在最右侧,就创建 LayoutData 填入 big 属性,
            // 否则就在右侧传入的现有的 LayoutData 中添加 big 属性
            return if (parentData == null) {
                LayoutData(big = big)
            } else {
                (parentData as LayoutData).apply { this.big = big }
            }
        }
    })

    fun Modifier.weightData(weight: Float) = then(object : ParentDataModifier {
        override fun Density.modifyParentData(parentData: Any?): Any? {
            // 如果 parentData 为空就创建 LayoutData,否则就沿用参数传入的 LayoutData,添加 weight 属性
            return ((parentData as? LayoutData) ?: LayoutData()).also { it.weight = weight }
        }
    })
}

3、实现原理

ParentDataModifier 与 DrawModifier、PointerInputModifier 一样,都是先被添加到它右侧距离它最近的 LayoutNodeWrapper 的 entities 这个数组的对应类型的链表中:

@kotlin.jvm.JvmInline
internal value class EntityList(
    val entities: Array<LayoutNodeEntity<*, *>?> = arrayOfNulls(TypeCount)
) {

    fun addBeforeLayoutModifier(layoutNodeWrapper: LayoutNodeWrapper, modifier: Modifier) {
        if (modifier is DrawModifier) {
            add(DrawEntity(layoutNodeWrapper, modifier), DrawEntityType.index)
        }
        if (modifier is PointerInputModifier) {
            add(PointerInputEntity(layoutNodeWrapper, modifier), PointerInputEntityType.index)
        }
        if (modifier is SemanticsModifier) {
            add(SemanticsEntity(layoutNodeWrapper, modifier), SemanticsEntityType.index)
        }
        // 注意 ParentDataModifier 添加的链表类型是 SimpleEntity
        if (modifier is ParentDataModifier) {
            add(SimpleEntity(layoutNodeWrapper, modifier), ParentDataEntityType.index)
        }
    }
}

与其他几个类型稍有不同的是,ParentDataModifier 对应的链表类型为 SimpleEntity,不像其他三种都是 XxxModifier 对应 XxxEntitity。

然后我们来看一下,ParentDataModifier 提供的数据 parentData 是被如何使用的。前面我们讲 CustomLayout 的示例时,是通过 Layout() 参数的 MeasurePolicy 接口的唯一抽象函数 measure() 的参数拿到 measurables: List<Measurable>,然后遍历 measurables 通过 Measurable 拿到 parentData 这个数据。

实际上 parentData 是 IntrinsicMeasurable 接口中的属性:

interface IntrinsicMeasurable {
    /**
     * Data provided by the [ParentDataModifier].
     */
    val parentData: Any?
    ...
}

LayoutNodeWrapper 实现了 IntrinsicMeasurable 的子接口 Measurable,因此 LayoutNodeWrapper 的内部有 parentData 的实现:

	// 对 parentData 的实现就是去拿 ParentDataEntity 链表头的 parentData 属性
	override val parentData: Any?
        get() = entities.head(EntityList.ParentDataEntityType).parentData

	// 链表头的 parentData
    private val SimpleEntity<ParentDataModifier>?.parentData: Any?
        get() = if (this == null) {
            wrapped?.parentData
        } else {
            with(modifier) {
                /**
                 * ParentData provided through the parentData node will override the data provided
                 * through a modifier.
                 */
                measureScope.modifyParentData(next.parentData)
            }
        }

实现 parentData 属性的方式就是去拿 ParentDataEntity 链表头的 parentData 属性,而链表头的 parentData 是 SimpleEntity 的扩展属性,它根据 SimpleEntity 是否为空,有两种处理方式:

  1. 当 SimpleEntity 为空时,返回当前 LayoutNodeWrapper 包装的另一个 LayoutNodeWrapper —— wrapped 的 parentData。也就是当前 LayoutNodeWrapper 内的 SimpleEntity 链表已经遍历完或者链表干脆就是空的,该去遍历内层 LayoutNodeWrapper 的 SimpleEntity 链表了
  2. 当 SimpleEntity 不为空时,调用该 SimpleEntity 内封装的 modifier 的 modifyParentData() 获取 parentData,该函数参数上的 next.parentData 是 SimpleEntity 链表的下一个节点的 parentData 属性,这里就形成了属性的递归调用,即当前节点在调用 modifyParentData() 前,需要先获取下一个节点的 parentData,获取下一个节点的 parentData 时,假如下一个节点不为空则获取下下一个节点的 parentData(如为空则走第 1 步去获取内层 LayoutNodeWrapper 的 parentData)

举一个例子帮助理解,假如有如下形式的 Modifier 链:

Modifier.then(ParentDataModifier1).then(ParentDataModifier2).then(LayoutModifier1)
        .then(ParentDataModifier3).then(ParentDataModifier4).then(LayoutModifier2)
        .then(ParentDataModifier5).then(ParentDataModifier6)

那么最外层的 LayoutNodeWrapper 的结构应该是:

ModifiedLayoutNode(
	entities = [null,null,null,ParentDataModifier1 -> ParentDataModifier2,null,null,null],
	modifier = LayoutModifier1,
	wrapped = ModifiedLayoutNode(
		entities = [null,null,null,ParentDataModifier3 -> ParentDataModifier4,null,null,null],
		modifier = LayoutModifier2,
		wrapped = InnerPlaceable(
			entities = [null,null,null,ParentDataModifier5 -> ParentDataModifier6,null,null,null]
		)
	)
)

当访问外层 LayoutNodeWrapper 的 parentData 属性时,它会取 entities 数组中索引为 3 的链表 SimpleEntity<ParentDataModifier> 的表头,再访问表头的 parentData 属性。

访问表头 parentData 属性的过程是一个递归过程,递归访问下一个 SimpleEntity<ParentDataModifier> 节点的 parentData 属性,如果到了链表尾部就或链表本身就是空的,就访问当前 ModifiedLayoutNode 的 wrapped 包装的下一个 ModifiedLayoutNode,这样一直递归下去直到 InnerPlaceable 的 SimpleEntity<ParentDataModifier> 链表的尾部。

递归返回时带着下一个节点的 parentData 作为参数调用 ParentDataModifier 接口的 modifyParentData(),在这个函数中对下一个节点获取到的 parentData 作为参数进行数据融合,返回的结果将作为它上一个节点的 modifyParentData() 的参数,直到最外层 ModifiedLayoutNode 的 parentData 得出一个计算结果。

4、总结

对于 ParentDataModifier 可以总结以下几点:

  • 作用:ParentDataModifier 用于给子组件附加一些属性,辅助父组件的测量与布局
  • 用法:只有在通过 Layout() 写自定义函数时才会用到 ParentDataModifier,在测量与布局的算法中通过 Measurable 的 parentData 属性拿到这个数据后根据具体需求使用即可,最后要提供一个 Modifier 函数实现 ParentDataModifier,在 modifyParentData() 中根据需求提供附加数据
  • 在同一个组件上交换调用 ParentDataModifier 函数的位置,UI 显示效果不会发生变化,因为使用 ParentDataModifier 提供的数据的是该组件的父组件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值