likes
comments
collection
share

Compose编程思想 -- ModifierLocal使用及原理分析

作者站长头像
站长
· 阅读数 10

相关文章:

前言

在之前的文章中,我介绍了CompositionLocal的使用,它具备@Composable函数的穿透作用,在CompositionLocalProvider提供的作用域内,任意@Composable函数都可以使用其提供的局部变量,具有移除函数参数的作用,但是只会在上下文、主题等场景下使用,如果有不熟悉的伙伴,可以查看文章开头的相关文章。

那么本节我将会介绍另外一种提供局部变量的方式,即ModifierLocal

1 ModifierLocal的使用

例如在Column中第一个子组件中,连续调用了两次layout,假设在第一个layout中创建一个成员变量firstWidth,想要在第二个layout中使用,其实也是一种穿透的效果。

@Composable
fun TestModifierLocal() {

    Column {
        Text(text = "第一个元素", modifier = Modifier
            .layout { measurable, constraints ->
                val size = measurable.measure(constraints)
                // 这是一个成员变量
                val firstWidth = size.width
                layout(size.width, size.height) {
                    size.placeRelative(0, 0)
                }
            }
            .layout { measurable, constraints ->
                val size = measurable.measure(constraints)
                layout(size.width, size.height) {
                    size.placeRelative(0, 0)
                }
            })
        Text(text = "第二个元素")
    }

}

1.1 ModifierLocalProvider介绍

在使用ModifierLocal的时候,也有一个用于提供共享参数的Provider -- ModifierLocalProvider

/**
 * A Modifier that can be used to provide [ModifierLocal]s that can be read by other modifiers to
 * the right of this modifier, or modifiers that are children of the layout node that this
 * modifier is attached to.
 */
@Stable
@JvmDefaultWithCompatibility
interface ModifierLocalProvider<T> : Modifier.Element {
    /**
     * Each [ModifierLocalProvider] stores a [ModifierLocal] instance that can be used as a key
     * by a [ModifierLocalConsumer] to read the provided value.
     */
    val key: ProvidableModifierLocal<T>

    /**
     * The provided value, that can be read by modifiers on the right of this modifier, and
     * modifiers added to children of the composable using this modifier.
     */
    val value: T
}

用于提供ModifierLocal,可以被当前Modifier右侧的Modifiers读到,即上游定义的ModifierLocal,在下游可以被读到。

ModifierLocalProvider也是一个Modifier.Element,它提供了key-value键值对,通过key可以取到定义的value,这个key其实就是ModifierLocal

Modifier.modifierLocalProvider(modifierLocalOf { "" }) { "共享数据" }

通过Modifier的modifierLocalProvider可以提供一个ModifierLocal用于给Modifier下游的Modifier提供局部变量的读取操作。

/**
 * A Modifier that can be used to provide [ModifierLocal]s that can be read by other modifiers to
 * the right of this modifier, or modifiers that are children of the layout node that this
 * modifier is attached to.
 */
@ExperimentalComposeUiApi
fun <T> Modifier.modifierLocalProvider(key: ProvidableModifierLocal<T>, value: () -> T): Modifier {
    return this.then(
        object : ModifierLocalProvider<T>,
            InspectorValueInfo(
                debugInspectorInfo {
                    name = "modifierLocalProvider"
                    properties["key"] = key
                    properties["value"] = value
                }
            ) {
            override val key: ProvidableModifierLocal<T> = key
            override val value by derivedStateOf(value)
        }
    )
}

那么既然有存操作,就会有取操作,取值的操作就是通过ModifierLocalConsumer,它是用来消费左侧的Modifier设置的ModifierLocal

/**
 * A Modifier that can be used to consume [ModifierLocal]s that were provided by other modifiers to
 * the left of this modifier, or above this modifier in the layout tree.
 */
@Stable
@JvmDefaultWithCompatibility
interface ModifierLocalConsumer : Modifier.Element {
    /**
     * This function is called whenever one of the consumed values has changed.
     * This could be called in response to the modifier being added, removed or re-ordered.
     */
    fun onModifierLocalsUpdated(scope: ModifierLocalReadScope)
}

1.2 ModifierLocal使用

前面我们知道了ModifierLocalProviderModifierLocalConsumer是用来提供数据和消费数据的,那么如何在上下游将其串联起来。

通过源码我们可以发现,ModifierLocalProviderModifierLocalConsumer都是Modifier.Element,因此可以采用CombinedModifier融合两个Element来达成效果。

@Composable
fun TestModifierLocal() {

    //需要共享的key
    val mKey = modifierLocalOf { "" }

    Column {
        Text(text = "第一个元素", modifier = Modifier
            .then(object : LayoutModifier, ModifierLocalProvider<String> {
                //需要共享的value
                var mValue: String? = null
                override fun MeasureScope.measure(
                    measurable: Measurable,
                    constraints: Constraints
                ): MeasureResult {
                    Log.d("TAG", "measure: left")
                    val size = measurable.measure(constraints)
                    // 在测量的时候,赋值
                    mValue = size.width.toString()
                    return layout(size.width, size.height) {
                        size.placeRelative(0, 0)
                    }
                }

                override val key: ProvidableModifierLocal<String>
                    get() = mKey
                override val value: String
                    get() = mValue ?: ""

            })
            .then(object : LayoutModifier, ModifierLocalConsumer {
                override fun MeasureScope.measure(
                    measurable: Measurable,
                    constraints: Constraints
                ): MeasureResult {
                    Log.d("TAG", "measure: right")
                    val size = measurable.measure(constraints)
                    return layout(size.width, size.height) {
                        size.placeRelative(0, 0)
                    }
                }

                override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) {
                    with(scope) {
                        Log.d("TAG", "onModifierLocalsUpdated: ${mKey.current}")
                    }
                }

            }))
        Text(text = "第二个元素")
    }

}

在上游通过Combined的方式实现了将LayoutModifierModifierLocalProvider的融合,注意这里在赋值mValue是在LayoutNode测量的时候,那么这种情况下,能保证下游取数据的时候,是在测量完成之后吗?

显然不能,除非在业务层做这种保障,像上述的这种方式是无法保证的,具体原因看下1.3小节对于源码的介绍。那么如何保证上游定义的局部变量,可以在下游使用的时候,准确地获取到?

最主要的原因就是,ModifierLocal不是这么用的,因为onModifierLocalsUpdated的调用是一定要比测量和布局早的,因此在测量过程中对变量操作是无效的,因此只有在onModifierLocalsUpdated中处理完数据,下游才能拿到处理完的数据,因此需要同时实现ModifierLocalProviderModifierLocalConsumer接口。

@Composable
fun TestModifierLocal() {

    //需要共享的key
    val mKey = modifierLocalOf { "" }

    Column {
        Text(
            text = "第一个元素", modifier = Modifier
                .then(object : LayoutModifier, ModifierLocalProvider<String>,
                    ModifierLocalConsumer {
                    //需要共享的value
                    lateinit var mValue: String
                    override fun MeasureScope.measure(
                        measurable: Measurable,
                        constraints: Constraints
                    ): MeasureResult {
                        val size = measurable.measure(constraints)
                        // 在测量的时候,赋值
                        return layout(size.width, size.height) {
                            size.placeRelative(0, 0)
                        }
                    }

                    override val key: ProvidableModifierLocal<String>
                        get() = mKey
                    override val value: String
                        get() = mValue

                    override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) {
                        with(scope) {
                            val initValue = mKey.current
                            mValue = initValue + "LEFT"
                        }
                    }

                })
                .then(object : LayoutModifier, ModifierLocalConsumer,
                    ModifierLocalProvider<String> {
                    lateinit var mValue: String
                    override fun MeasureScope.measure(
                        measurable: Measurable,
                        constraints: Constraints
                    ): MeasureResult {
                        val size = measurable.measure(constraints)
                        return layout(size.width, size.height) {
                            size.placeRelative(0, 0)
                        }
                    }

                    override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) {
                        with(scope) {
                            mValue = mKey.current
                            Log.d("TAG", "onModifierLocalsUpdated: ${mKey.current}")
                        }
                    }

                    override val key: ProvidableModifierLocal<String>
                        get() = mKey
                    override val value: String
                        get() = mValue

                })
        )
        Text(text = "第二个元素")
    }

}

在上游的onModifierLocalsUpdated中对mValue做初始化的操作,在下游的onModifierLocalsUpdated中就可以拿到对应的值。

在Compose中,其实提供了一些与ModifierLocal相关的函数,例如windowInsetsPadding,就是实现了ModifierLocalProviderModifierLocalConsumer接口,下面我也是使用了这种方式实现一个可以附加padding的需求。

// 共享的padding key
internal val ModifierLocalConsumerPaddingKey = modifierLocalOf { 0.dp }

/**
 * 可重复累加的padding
 */
fun Modifier.multiPadding(padding: Dp) =
    then(object : LayoutModifier, ModifierLocalProvider<Dp>, ModifierLocalConsumer {

        var paddingValue: Dp = 0.dp

        override fun MeasureScope.measure(
            measurable: Measurable,
            constraints: Constraints
        ): MeasureResult {
            val placeable = measurable.measure(constraints)
            //加padding
            val width = paddingValue.value + placeable.width * 2
            val height = paddingValue.value + placeable.height * 2
            return layout(width.toInt(), height.toInt()) {
                placeable.placeRelative(0, 0)
            }
        }


        override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) {
            with(scope) {
                val current = ModifierLocalConsumerPaddingKey.current
                // 上下游的padding累加
                paddingValue = current + padding
            }
        }

        override val key: ProvidableModifierLocal<Dp>
            get() = ModifierLocalConsumerPaddingKey
        override val value: Dp
            get() = paddingValue

    })

其实使用ModifierLocal可以用来实现Modifier存在上下联动的这种场景中,下游数据会依赖上游数据的时候,可以像Flow这样,在上游将数据处理完成之后,可以在下游拿到处理完成的数据。

1.3 ModifierLocal原理分析

在Modifier初始化的时候,如果按照1.2中我自定义的Modifier,那么在Modifier.ElementModifier.Node的过程中,会创建BackwardsCompatNode,在调用onAttach函数时,会执行initializeModifier函数。


// BackwardsCompatNode.kt

override fun onAttach() {
    initializeModifier(true)
}
private fun initializeModifier(duringAttach: Boolean) {
    check(isAttached)
    val element = element
    if (isKind(Nodes.Locals)) {
        if (element is ModifierLocalProvider<*>) {
            updateModifierLocalProvider(element)
        }
        if (element is ModifierLocalConsumer) {
            if (duringAttach)
                updateModifierLocalConsumer()
            else
                sideEffect { updateModifierLocalConsumer() }
        }
    }
    // ...... 
}

在这个函数中,首先会判断当前节点如果是ModifierLocalProvider,那么会执行updateModifierLocalProvider函数,这个函数主要是用来将定义的key存储到ModifierLocalManager中的一个数组中,其实就是将ModifierLocal存到一个集合里。

private fun updateModifierLocalProvider(element: ModifierLocalProvider<*>) {
    val providedValues = _providedValues
    if (providedValues != null && providedValues.contains(element.key)) {
        providedValues.element = element
        requireOwner()
            .modifierLocalManager
            .updatedProvider(this, element.key)
    } else {
        _providedValues = BackwardsCompatLocalMap(element)
        // we only need to notify the modifierLocalManager of an inserted provider
        // in the cases where a provider was added to the chain where it was possible
        // that consumers below it could need to be invalidated. If this layout node
        // is just now being created, then that is impossible. In this case, we can just
        // do nothing and wait for the child consumers to read us. We infer this by
        // checking to see if the tail node is attached or not. If it is not, then the node
        // chain is being attached for the first time.
        val isChainUpdate = requireLayoutNode().nodes.tail.isAttached
        if (isChainUpdate) {
            requireOwner()
                .modifierLocalManager
                .insertedProvider(this, element.key)
        }
    }
}

然后,再判断如果当前节点为ModifierLocalConsumer,那么就会执行其onModifierLocalsUpdated函数。

fun updateModifierLocalConsumer() {
    if (isAttached) {
        readValues.clear()
        requireOwner().snapshotObserver.observeReads(
            this,
            updateModifierLocalConsumer
        ) {
            (element as ModifierLocalConsumer).onModifierLocalsUpdated(this)
        }
    }
}

所以从源码中就可以看出,onModifierLocalsUpdated的执行发生在Modifier初始化的过程中,还没有到measurelayout的过程中,所以这也印证了我前面的结论。因此对于Modifier共享的数据,只能在onModifierLocalsUpdated中完成初始化,在测量绘制的过程中调用,不可做赋值处理。

转载自:https://juejin.cn/post/7353280369381228570
评论
请登录