Compose 实践与探索七 —— Modifier 基础

从本篇文章开始我们要讲解 Modifier 修饰符,它是一个很精妙的东西,允许我们通过链式调用的方式为组件应用一系列的样式设置,如边距、字体、位移等。在 Compose 中,每个基础的 Composable 组件都有一个 modifier 参数,通过传入自定义的 Modifier 修改组件的样式。

此外,前面我们还提过 Modifier 调用链对顺序敏感,不同的调用顺序会产生不同的 Modifier 链从而影响最终的 UI 效果,这是 Compose 按照 Modifier 链来顺序完成页面布局与绘制的结果。那么 Modifier 链是如何被构建并解析的呢?本系列文章会深入讲解 Modifier 链背后的实现原理。

Modifier 系列使用的是 1.3.0 - alpha01 版本的源码,后续如有更新会显式声明。

1、Modifier 接口与伴生对象

以下是 Modifier 接口的全部内容:

interface Modifier {
    /**
     * 从 [initial] 初始值开始累积值,通过【从外向内】依次对当前值和每个元素应用 [operation] 操作。
     *
     * 元素在链表中从左到右依次包裹;在 `+` 表达式或 [operation] 参数顺序中位于左侧的 [Element] 
     * 会影响其后所有元素。[foldIn] 可用于从父节点或修饰符链头部开始,向最终包裹的子节点累积值。
     */
    fun <R> foldIn(initial: R, operation: (R, Element) -> R): R

    /**
     * 从 [initial] 初始值开始累积值,通过【从内向外】依次对当前值和每个元素应用 [operation] 操作
     *
     * 元素在链表中从左到右依次包裹;在 `+` 表达式或 [operation] 参数顺序中位于左侧的 [Element]
     * 会影响其后所有元素。[foldOut] 可用于从子节点或修饰符链尾部开始,向父节点或头部累积值
     */
    fun <R> foldOut(initial: R, operation: (Element, R) -> R): R

    /**
     * 若修饰符链中任意 [Element] 满足 [predicate] 条件则返回 true
     */
    fun any(predicate: (Element) -> Boolean): Boolean

    /**
     * 若修饰符链中所有 [Element] 满足 [predicate] 条件,或修饰符链为空则返回 true
     */
    fun all(predicate: (Element) -> Boolean): Boolean

    /**
     * 将当前修饰符与另一个修饰符连接
     *
     * 返回表示当前修饰符后接 [other] 的新 [Modifier]
     */
    infix fun then(other: Modifier): Modifier =
        if (other === Modifier) this else CombinedModifier(this, other)

    /**
     * 修饰符链中的单个元素
     */
    @JvmDefaultWithCompatibility
    interface Element : Modifier { ... }

    /**
     * 伴生对象 Modifier 表示空的默认修饰符,不包含任何 [Element] 元素。
     * 可通过修饰符扩展函数创建新修饰符:
     *
     * @sample androidx.compose.ui.samples.ModifierUsageSample
     *
     * 或作为 [Modifier] 参数的默认值:
     *
     * @sample androidx.compose.ui.samples.ModifierParameterSample
     */
    // 伴生对象实现 `Modifier` 以便作为修饰符扩展表达式的起点
    companion object : Modifier {
        override fun <R> foldIn(initial: R, operation: (R, Element) -> R): R = initial
        override fun <R> foldOut(initial: R, operation: (Element, R) -> R): R = initial
        override fun any(predicate: (Element) -> Boolean): Boolean = false
        override fun all(predicate: (Element) -> Boolean): Boolean = true
        override infix fun then(other: Modifier): Modifier = other
        override fun toString() = "Modifier"
    }
}

Modifier 接口内定义了:

  • 两组接口函数:
    • foldIn() 与 foldOut() 负责遍历 Modifier 链:foldIn() 是正向遍历,从左向右遍历 Modifier 链; foldOut() 是反向遍历,从右向左遍历 Modifier 链,遍历时会执行 operation 指定的操作并累积结果
    • any() 与 all() 负责条件判断:any() 是 Modifier 链上有任意一个 Element 满足 predicate 条件就返回 true,all() 是 Modifier 链上所有 Element 都满足 predicate 才返回 true
  • 一个默认函数:then() 负责连接两个 Modifier,将它们合并到一个 CombinedModifier 中,只有 Modifier 的伴生对象重写了该函数
  • 一个子接口:Element 接口是 Modifier 的子接口,表示一个单个的 Modifier 元素(与 CombinedModifier 这种融合的 Modifier 相对),我们写的 Modifier 链上几乎所有能用到的 Modifier 都是 Element 的实现类,下一节专门介绍它时再看其具体内容
  • 一个伴生对象 Modifier:提供默认实现的 Modifier 对象,常用于 Modifier 链的起点以及函数参数的默认值

本节我们主要来说 Modifier 的伴生对象,从它的两个作用:Modifier 链的起点、函数参数的默认值讲起。

1.1 Modifier 作为调用链起点

Kotlin 使用 companion object 声明一个伴生对象,伴生对象有一个特性,就是在哪个类或接口内声明了这个伴生对象,就可以用这个类或接口的名字代表这个伴生对象。比如对于 Modifier 接口而言,访问其伴生对象的完整写法是 Modifier.Companion,但由于伴生对象的特性,Companion 可以省略,因此 Modifier 单拿出来就可以表示这个接口的伴生对象。

伴生对象的 Modifier 实现了 Modifier 接口,这不是伴生对象常用的声明方式,而是 Compose 为了让伴生对象作为 Modifier 调用链的起点,专门这么写的。此外,伴生对象实现 Modifier 接口时,只给了默认实现,或者说一个最基础的实现,比如 foldIn() 与 foldOut() 直接返回 initial 参数,any() 和 all() 无视 predicate 参数直接返回一个 Boolean 字面值,这样实现一个“白板” Modifier 对象是为了不对 Modifier 调用链的结果产生任何影响。

1.2 Modifier 作为函数参数默认值

至于让伴生对象的 Modifier 作为函数参数的默认值,也是为了便于调用者的灵活使用,提升易用性。

比如现在自定义一个 Composable 展示一个定制的 Box:

@Composable
fun Custom() {
    Box(
        Modifier
            .size(30.dp)
            .background(Color.Blue)
    )
}

为了让调用方进一步控制 Box 的外观,需要让 Custom() 暴露一个 Modifier 参数给外界:

@Composable
fun Custom(modifier: Modifier) {
    Box(
        modifier
            .size(30.dp)
            .background(Color.Blue)
    )
}

假如调用方就想使用默认的 Box,那就不需要传 modifier 参数,因此要给 modifier 一个默认值:

// = 左侧的 Modifier 表示接口类型,= 右侧的 Modifier 是伴生对象
@Composable
fun Custom(modifier: Modifier = Modifier) {
    Box(
        modifier
            .size(30.dp)
            .background(Color.Blue)
    )
}

Compose 官方建议将 Modifier 作为可组合函数中第一个有默认值的参数,因为这样的参数有一个特权,就是在调用函数时可以不用填它的参数名,就像上面的 Custom() 内的 Box,不用写成 Box(modifier = modifier.size().background())

2、Modifier 的链式调用

本节会介绍促成 Modifier 链式调用的函数与类/接口,它们就是上一节贴出的 Modifier 接口内定义的内容。

2.1 then()

在 1.1 节中我们已经讲过了 Modifier 调用链的起点就是 Modifier 的伴生对象,而 Modifier 链后面的部分则都是通过 then() 来连接的:

@Suppress("ModifierFactoryExtensionFunction")
@Stable
@JvmDefaultWithCompatibility
interface Modifier {
    // 若 other 与当前 Modifier 不是同一个对象则将二者合并到 CombinedModifier 中返回
    infix fun then(other: Modifier): Modifier =
        if (other === Modifier) this else CombinedModifier(this, other)
}

then() 通过接收一个 Modifier 参数 other,将当前的 Modifier 对象与 other 合并到 Modifier 接口的直接实现类 CombinedModifier 中,最后返回 CombinedModifier 的方式实现了 Modifier 调用链的连接。而作为 Modifier 链的起点,伴生对象的 then() 就是直接取它后面的 Modifier:

	// 当前版本只有 Modifier 的伴生对象重写了 then()
	companion object : Modifier {
        override infix fun then(other: Modifier): Modifier = other
    }

链式调用的原理就是以上内容。下面我们再看看常用的 Modifier 链上的函数都是如何使用 then() 的:

fun Modifier.background(
    color: Color,
    shape: Shape = RectangleShape
) = this.then(
    Background(
        color = color,
        shape = shape,
        inspectorInfo = debugInspectorInfo {
            name = "background"
            value = color
            properties["color"] = color
            properties["shape"] = shape
        }
    )
)

@Stable
fun Modifier.size(size: Dp) = this.then(
    SizeModifier(
        minWidth = size,
        maxWidth = size,
        minHeight = size,
        maxHeight = size,
        enforceIncoming = true,
        inspectorInfo = debugInspectorInfo {
            name = "size"
            value = size
        }
    )
)

@Stable
fun Modifier.padding(
    horizontal: Dp = 0.dp,
    vertical: Dp = 0.dp
) = this.then(
    PaddingModifier(
        start = horizontal,
        top = vertical,
        end = horizontal,
        bottom = vertical,
        rtlAware = true,
        inspectorInfo = debugInspectorInfo {
            name = "padding"
            properties["horizontal"] = horizontal
            properties["vertical"] = vertical
        }
    )
)

都是 this.then(),然后给 then() 传一个 Modifier 对象,这里不论是 Background、SizeModifier 还是 PaddingModifier,都是 Modifier 接口的实现类,后面会在相应的章节中解析它们的原理。

2.2 CombinedModifier 与 Modifier.Element

之所以要将二者放在一起说,是因为 Modifier.Element 是为 CombinedModifier 服务的。Modifier.Element 在继承 Modifier 时,将其设计为一个单体的 Modifier,内部不包含任何其他的 Modifier 对象:

	/**
	* 一个包含在 [Modifier] 链中的单个元素
	*/
	@JvmDefaultWithCompatibility
    interface Element : Modifier {
        override fun <R> foldIn(initial: R, operation: (R, Element) -> R): R =
            operation(initial, this)
    
        override fun <R> foldOut(initial: R, operation: (Element, R) -> R): R =
            operation(this, initial)
    
        override fun any(predicate: (Element) -> Boolean): Boolean = predicate(this)
    
        override fun all(predicate: (Element) -> Boolean): Boolean = predicate(this)
    }

Element 的“单个元素”是与 CombinedModifier 这种复合的 Modifier 相对而言的。由于内部不包含其他的 Modifier 对象,因此在继承 Modifier 的接口函数时,只考虑自己即可。比如遍历功能的两个函数,它只需要在 operation() 中传入自己 this,判断功能的两个函数也只对自己 this 做 predicate() 判断就可以了。

而 CombinedModifier 是将两个 Modifier 合并到一起,因此在实现时需要让两个 Modifier 都参与计算:

class CombinedModifier(
    internal val outer: Modifier,
    internal val inner: Modifier
) : Modifier {
    // 从左至右正序遍历 Modifier 链。先计算 outer,outer 计算结果作为后续计算 inner 的初始值
    override fun <R> foldIn(initial: R, operation: (R, Modifier.Element) -> R): R =
        inner.foldIn(outer.foldIn(initial, operation), operation)

    // 与 foldIn() 方向相反,是从内层 Modifier 开始向外层进行计算,逆序从右向左遍历
    override fun <R> foldOut(initial: R, operation: (Modifier.Element, R) -> R): R =
        outer.foldOut(inner.foldOut(initial, operation), operation)

    // 任一匹配
    override fun any(predicate: (Modifier.Element) -> Boolean): Boolean =
        outer.any(predicate) || inner.any(predicate)

    // 全量匹配
    override fun all(predicate: (Modifier.Element) -> Boolean): Boolean =
        outer.all(predicate) && inner.all(predicate)
	
    ...
}

CombinedModifier 中的 outer 是调用函数的一方,而 inner 是函数参数。比如 modifer.background(),outer 就是 modifier,inner 就是 Background。由于 outer 和 inner 都有可能是 CombinedModifier 这种复合的 Modifier,因此在进行计算时,需要做相应的递归计算,直到遇到单个的 Modifier,也就是 Element,才能计算出一个具体的结果,然后将结果向上合并,得出最终结果。

我们需要清楚一件事,无论 Modifier 链上有多少个 CombinedModifier,它最终总会有 Element 作为叶子节点帮你计算出一个具体的结果,然后再走递归返回的过程,计算出过程中的每一个中间结果,直到最终结果。所以我们才说,Element 就是作为 CombinedModifier 的叶子节点处理单个 Modifier 的辅助类,除了 Modifier 的伴生对象以及 CombinedModifier,
其余几乎所有的 Modifier 都会直接或间接地实现 Element。

提到 Modifier 的伴生对象,我们再回头看一下它的实现:

	companion object : Modifier {
        // 计算组的函数直接返回初始值,作为下一次计算的初始值,没有做多余计算影响结果
        override fun <R> foldIn(initial: R, operation: (R, Element) -> R): R = initial
        override fun <R> foldOut(initial: R, operation: (Element, R) -> R): R = initial、
        // 判断组的函数,任一判断返回 false、全量判断返回 true 作为后续的初值,将链的计算结果交给后面
        override fun any(predicate: (Element) -> Boolean): Boolean = false
        override fun all(predicate: (Element) -> Boolean): Boolean = true
        // Modifier.then(other) 的结果就是 other,相当于忽略了伴生对象的 Modifier
        override infix fun then(other: Modifier): Modifier = other
        override fun toString() = "Modifier"
    }

你会发现,它的默认实现都不会影响 Modifier 链条的计算结果,除了在占位时有些存在感,其他情况下基本上就是个“透明人”。

Modifier 接口的直接子类或子接口有三个,分别是实现类 CombinedModifier、伴生对象 Modifier.Companion 以及子接口 Modifier.Element,本节过后它们已经全部登场了,后续的内容还是在它们的基础上发展的。

3、Modifier.composed() 与 ComposedModifier

2.1 节我们提到,Modifier 调用链的连接主要是靠 then()。但除了 then() 其实还有一个 Modifier 函数可以起到连接 Modifier 的作用,就是 composed()。当然了,该函数实际上是在内部调用了 then() 才具备连接功能的,而且,composed() 的主要用途也并不是连接 Modifier,而是实现有状态的 Modifier 并让该 Modifier 可以在多个地方复用。

下面让我们以 composed() 作为切入点,看看如何实现一个有状态的 Modifier 并复用它。

3.1 composed() 与 ComposedModifier 概况

composed() 必传一个生产 Modifier 的 Composable 工厂函数 factory:

/**
 * 声明一个为每个被修饰元素实时组合的 [Modifier]。通过 [composed] 可实现【有状态修饰符】,
 * 此类修饰符会为每个被修饰元素维护实例专属的状态,使得同一 [Modifier] 实例可安全地复用于多个元素,
 * 同时保持各元素的独立状态。
 *
 * 若指定 [inspectorInfo],此修饰符将在开发工具中可见。需在此参数中指定原始修饰符的名称及参数。
 *
 * 示例用法可参考:
 * @sample androidx.compose.ui.samples.InspectorInfoInComposedModifierSample
 * @sample androidx.compose.ui.samples.InspectorInfoInComposedModifierWithArgumentsSample
 *
 * 若直接将 [Modifier] 应用到元素树节点,必须调用 [materialize] 以创建实例专属的修饰符。
 */
fun Modifier.composed(
    inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo,
    factory: @Composable Modifier.() -> Modifier
): Modifier = this.then(ComposedModifier(inspectorInfo, factory))

它将 factory 传入 ComposedModifier 后,使用 then() 将 ComposedModifier 连接到整个 Modifier 链上便结束了自己的工作。实际上,composed() 就是一个连接 ComposedModifier 的辅助函数,因为 ComposedModifier 是一个 private 类,开发者无法直接获取它,只能通过 composed() 使用它:

private open class ComposedModifier(
    inspectorInfo: InspectorInfo.() -> Unit,
    // 创建 Modifier 的工厂函数
    val factory: @Composable Modifier.() -> Modifier
) : Modifier.Element, InspectorValueInfo(inspectorInfo)

ComposedModifier 是一个比较特殊的 Modifier,它本身没有提供什么实际功能,它只是将我们提供的 Modifier 包在一个工厂函数里,而不是直接、立即创建这个 Modifier。在 Compose 进行组合时,才会运行工厂函数生产出我们提供的 Modifier 对象。

比如说:

val modifier1 = Modifier.padding(8.dp)
val modifier2 = Modifier.composed { Modifier.padding(8.dp) }

对于 modifier1 而言,当代码执行到 Modifier.padding() 时会立即生成一个 PaddingModifier 然后赋值给 modifier1;而对于 modifier2,它只会立即得到一个 ComposedModifier,而 composed() 的尾随 lambda 表达式作为其 factory 参数被传入更底层的代码中,不会立即被执行,等到时机成熟,执行 factory 工厂函数时,才会生产出 PaddingModifier。

至于什么是成熟的时机,我们马上来看。

3.2 工厂函数执行流程

由于 ComposedModifier 的工厂函数 factory 实际上是在组合阶段被执行的,需要放在具体的 Composable 组件中才能分析具体的代码流程,因此我们将 ComposedModifier 放在 Box 中作为示例:

Box(Modifier.composed { Modifier.padding(8.dp) })

Box() 的参数是一个 ComposedModifier,进入 Box():

@Composable
fun Box(modifier: Modifier) {
    Layout({}, measurePolicy = EmptyBoxMeasurePolicy, modifier = modifier)
}

执行 Layout(),ComposedModifier 被传入 materializerOf():

@Suppress("ComposableLambdaParameterPosition")
@UiComposable
@Composable inline fun Layout(
    content: @Composable @UiComposable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {
    ...
    ReusableComposeNode<ComposeUiNode, Applier<Any>>(
        ...,
        skippableUpdate = materializerOf(modifier),
        content = content
    )
}

materializerOf() 又把 ComposedModifier 向下传给 Composer 的 materialize():

@PublishedApi
internal fun materializerOf(
    modifier: Modifier
): @Composable SkippableUpdater<ComposeUiNode>.() -> Unit = {
    val materialized = currentComposer.materialize(modifier)
    update {
        set(materialized, ComposeUiNode.SetModifier)
    }
}

Composer 的 materialize() 会将逻辑修饰符转换为具体的可应用于元素的实例,主要是对 ComposedModifier、FocusEventModifier 与 FocusRequesterModifier 进行单独的处理。当正向遍历 Modifier 链遇到这三种 Modifier 时,执行它们保存的工厂函数 factory 得到开发者提供的 Modifier 并连接到被遍历的 Modifier 链上:

@Suppress("ModifierFactoryExtensionFunction")
fun Composer.materialize(modifier: Modifier): Modifier {
    // 如果 modifier 不是 ComposedModifier、FocusEventModifier、FocusRequesterModifier 则直接返回
    if (modifier.all {
            it !is ComposedModifier && it !is FocusEventModifier && it !is FocusRequesterModifier
        }
    ) {
        return modifier
    }
    ...

    // foldIn() 正向遍历 modifier,遍历时判断每个 Modifier 的实际类型,并调用相应的工厂函数
    // 生产出新的 Modifier 
    val result = modifier.foldIn<Modifier>(Modifier) { acc, element ->
        acc.then(
            if (element is ComposedModifier) {
                // 取出 ComposedModifier 的 factory 强转成它声明的函数类型
                @kotlin.Suppress("UNCHECKED_CAST")
                val factory = element.factory as Modifier.(Composer, Int) -> Modifier
                // 执行 factory 生产出开发者提供的 Modifier
                val composedMod = factory(Modifier, this, 0)
                // 递归调用自己,对 composedMod 内可能包含的 ComposedModifier 做相同的处理
                materialize(composedMod)
            } else {
                var newElement: Modifier = element
                if (element is FocusEventModifier) {
                    // 对 factory 进行类型转换后调用它
                    @Suppress("UNCHECKED_CAST")
                    val factory = WrapFocusEventModifier
                        as (FocusEventModifier, Composer, Int) -> Modifier

                    newElement = newElement.then(factory(element, this, 0))
                }
                // The same is true for FocusRequesterModifier and focusTarget()
                if (element is FocusRequesterModifier) {
                    @Suppress("UNCHECKED_CAST")
                    val factory = WrapFocusRequesterModifier
                        as (FocusRequesterModifier, Composer, Int) -> Modifier

                    newElement = newElement.then(factory(element, this, 0))
                }
                newElement
            }
        )
    }

    endReplaceableGroup()
    return result
}

到这一步能看出 ComposedModifier 的工厂函数是在组合过程中,当正向遍历 Modifier 链时被执行生产出开发者指定的 Modifier 并连接回 Modifier 链中。相当于用 ComposedModifier 的工厂函数生产出的 Modifier 替换掉 ComposedModifier。

这样我们再回头看使用 composed() 生成的 Modifier 与不使用 composed() 生成的 Modifier 有什么区别:

val modifier1 = Modifier.composed { Modifier.padding(8.dp) }
val modifier2 = Modifier.padding(8.dp)

唯一区别是使用 composed() 会修改其内部的 Modifier 生成的时间节点(或者说延迟到组合阶段),在显示上,二者没有区别。

3.3 有状态的 Modifier

了解工厂函数的执行流程,实际上是为了便于理解有状态的 Modifier 之间相互独立的特性。回看 composed() 的注释内容,它提醒我们 composed() 就是用来创建有状态的 Modifier 的,并且它们在不同的组件间相互独立:

声明一个 Modifier 的即时组合(just-in-time composition),该组合将用于修改每个元素。composed 可用于实现具有每个修改元素的实例特定状态的有状态修饰符(stateful modifiers),允许安全地重用相同的 Modifier 实例用于多个元素,同时保持元素特定的状态。

Modifier 的状态与 Composable 函数的状态是一个意思,指其内部有一个或多个变量,这些变量就是它的状态。

像上面的 modifier2,它就相当于一个没有状态的 PaddingModifier。为什么说是“相当于没有状态”呢?因为 PaddingModifier 虽然定义了 5 个成员属性,但都是 val 的,这些内部状态不可变:

private class PaddingModifier(
    val start: Dp = 0.dp,
    val top: Dp = 0.dp,
    val end: Dp = 0.dp,
    val bottom: Dp = 0.dp,
    val rtlAware: Boolean,
    inspectorInfo: InspectorInfo.() -> Unit
)

既然 PaddingModifier “没有”状态,那么包含它的 ComposedModifier 也没有状态。所以我们换一种写法,让 ComposedModifier 有状态:

val modifier = Modifier.composed {
    var padding = 8.dp
    Modifier.padding(padding) 
}

将一个有状态的 Modifier 用于多个组件中:

Box(modifier)
Text("James", modifier)

我们刚刚分析过 ComposedModifier 工厂函数的执行流程,它会在组件的组合阶段被执行并生产出指定的 Modifier。因此,Box 与 Text 内会各有一份相互独立的 PaddingModifier。这意味着,有状态的 Modifier 的状态是相互独立,各自管理的。这样便可以使用 composed() 将同一个 Modifier 用于多个不同的组件中,并且分别对它们做状态管理:

setContent {
    val modifier = Modifier.composed {
        // 没有 remember(),clickable 对 padding 的赋值就会被初始化覆盖掉
        var padding by remember { mutableStateOf(8.dp) }
        Modifier
        .padding(padding)
        .clickable { padding = 0.dp }
    }
    Box(modifier)
    Text(text = "Test", modifier)
}

由于 modifier 是会进入到 Composable 组件内部运行,而组件会进行重组,为了让 modifier 内的状态不在重组时被重复初始化,因此需要借助 remember() 缓存 padding 的状态值。

最后我们将有状态与无状态的 Modifier 做一个对比:

setContent {
    val modifier1 = Modifier.composed {
        var padding by remember { mutableStateOf(8.dp) }
        Modifier
        	.padding(padding)
        	.clickable { padding = 0.dp }
    }

    var padding by remember { mutableStateOf(8.dp) }
    val modifier2 = Modifier
    	.padding(padding)
    	.clickable { padding = 0.dp }

    Column {
        Box(Modifier.background(Color.Blue) then modifier1)
        Text(text = "Test", Modifier.background(Color.Green) then modifier1)
    }
}

modifier1 使用 composed(),而 modifier2 不使用 composed(),分别将它们放入 Column 的组件中做对比测试,发现使用 modifier1 时,点击某个组件,只会改变它自己的 padding;而使用 modifier2 时,点击一个组件,所有组件的 padding 都变为 0。

这是因为 composed() 创建的 Modifier 在每个组件运行时,都会通过工厂函数创建一个单独的 Modifier 对象仅供该组件使用,组件间的 Modifier 各自独立,相互之间没有影响。而不使用 composed() 创建的 Modifier 是立即创建,在多个组件中共享使用,一个组件修改了 Modifier 会影响到所有使用该 Modifier 的组件。

3.4 用途

composed() 更多是用在自定义 Modifier 中:

fun Modifier.paddingJumpModifier() = composed {
    var padding by remember { mutableStateOf(8.dp) }
    Modifier
        .padding(padding)
        .clickable { padding = 0.dp }
}

composed() 的 factory 参数打了 @Composable 注解,因此可以提供调用 Composable 函数的上下文环境,可以在其内部调用 remember() 等需要 @Composable 调用环境的函数。

此外,在需要使用协程或 CompositionLocal 时,也可以通过 composed() 提供上下文环境:

fun Modifier.coroutineModifier() = composed {
    // 通常使用协程时也是与某个状态搭配使用
    var status by remember { mutableStateOf(0) }
    LaunchedEffect(status) {
        
    }
    // 返回值需是 Modifier
    Modifier
}

fun Modifier.localModifier() = composed {
    // 访问 CompositionLocal,由于 get() 是 Composable 函数,因此需要一个 @Composable 环境
    LocalContext.current
    Modifier
}

源码中使用了 ComposedModifier 的地方:

@JvmDefaultWithCompatibility
interface AnimatedVisibilityScope {
    @ExperimentalAnimationApi
    fun Modifier.animateEnterExit(
        enter: EnterTransition = fadeIn() + expandIn(),
        exit: ExitTransition = fadeOut() + shrinkOut(),
        label: String = "animateEnterExit"
    ): Modifier = composed(
        inspectorInfo = debugInspectorInfo {
            name = "animateEnterExit"
            properties["enter"] = enter
            properties["exit"] = exit
            properties["label"] = label
        }
    ) {
        // 因为要调用 createModifier() 这个 @Composable 函数,所以用了 composed()
        this.then(transition.createModifier(enter, exit, label))
    }
}

再看 AnimationModifier,因为要用到 rememberCoroutineScope() 与 remember(),所以用了 composed():

fun Modifier.animateContentSize(
    animationSpec: FiniteAnimationSpec<IntSize> = spring(),
    finishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null
): Modifier = composed(
    inspectorInfo = debugInspectorInfo {
        name = "animateContentSize"
        properties["animationSpec"] = animationSpec
        properties["finishedListener"] = finishedListener
    }
) {
    // TODO: Listener could be a fun interface after 1.4
    val scope = rememberCoroutineScope()
    val animModifier = remember(scope) {
        SizeAnimationModifier(animationSpec, scope)
    }
    animModifier.listener = finishedListener
    this.clipToBounds().then(animModifier)
}

3.5 总结

最后我们来总结一下 Modifier.composed():

  1. 可以实时的创造出带有状态的 Modifier,并让这个 Modifier 在多个位置被复用
    • 状态是指 remember() 包着的变量作为 Modifier 的内部状态来使用
    • 复用是指 composed() 会在每一个使用处创建一个独立的 Modifier,每一个 Modifier 的内部状态就都是相互独立的,而不是在多处共享的
  2. 由于是在组合过程中创建 Modifier,因此工厂函数被做成 Composable 函数,可以在其内部调用其他 Composable 函数
  3. 当自定义 Modifier 时如果需要调用 Composable 函数,原则上,就给自定义的 Modifier 包一层 composed() 即可
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值