Jetpack Compose ComposedModifier

358 阅读7分钟

ComposedModifier

What is it?这是啥?

在 Compose 宇宙🌌里面,有很多的 Modifier,大致可以分为 3 类:

  1. 没有实际作用的 Modifier 单例对象,纯粹作为链式调用的起点;
  2. 对可组合内容起真正修饰作用的 Element 接口实现类,如 SizeElement 用于设置尺寸、PaddingElement 用于设置边距...;
  3. 用于合并 Modifier 的 CombinedModifier。

Element 继承树.jpg

在众多 Element 实现类中,有一个特殊的 ComposedModifier,它自身并没有修饰可组合内容的能力。查看 ComposedModifier 的源码,很简单,只声明了 1 个成员变量 factory,而且没有任何其他成员方法。

// ComposedModifier.kt 
// 并没有省略其他代码,ComposedModifier 类就是这么简单
private open class ComposedModifier(
    inspectorInfo: InspectorInfo.() -> Unit,
    val factory: @Composable Modifier.() -> Modifier
) : Modifier.Element, InspectorValueInfo(inspectorInfo)

构造函数里的 inspectorInfo 是用于调试的,我们不用关心。

成员变量 factory 是一个函数类型,拥有 @Composable 上下文,接收者是 Modifier,返回值也是 Modifier。我们可以简单地理解为:factory 就是一段能生成 Modifier 实例的代码。

  • ComposedModifier 是一个 Modifier(间接实现了 Modifier 接口);
  • ComposedModifier 自身不具备修饰可组合内容的能力;
  • ComposedModifier 的内部保存了一段能生成 Modifier 实例的代码。

基于这几点,我们可以认为:ComposedModifier 是一个可以延迟初始化 Modifier 的 Modifier。可以延迟初始化 Modifier 的 Modifier,再读一遍,还是很懵。既然懵,那干脆先别管有什么用,就先看它是怎么用吧,说不定看到用法就理解了作用呢。


How to use?怎么用?

使用 ComposedModifier 的方式很简单,调用 Modifier.composed() 函数,传入一个函数参数,就是 ComposedModifier 里面的 factory

// ComposedModifier.kt
fun Modifier.composed(
    inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo,
    factory: @Composable Modifier.() -> Modifier
): Modifier = this.then(ComposedModifier(inspectorInfo, factory))

Modifier.composed() 背后会使用 then() 将 ComposedModifier 添加到 Modifier 链中。

那么我们在创建 Modifier 的时候,就可以这么写:

val composedModifier = Modifier.composed { size(100.dp) }
val normalModifier = Modifier.size(100.dp)

和普通创建 modifier 的方式相比,区别在于:

  • 执行 Modifier.size(100.dp) 的过程中会立刻创建出一个 SizeElement
  • 执行 Modifier.composed { size(100.dp) } 过程中并不会立刻创建出一个 SizeElement,而是创建出一个 ComposedModifier,这个 ComposedModifier 内部保存了一段能生成 SizeElement 的代码,也就是 factory。只有当 ComposedModifier 被使用的时候,才会执行 factory 生成 SizeElement。

被使用的时候才会执行 factory 生成 SizeElement。被使用的时候?所谓何时?

Box(modifier = composedModifier)

我们把 modifier 作为参数传给可组合内容,在可组合内容发生组合(Composition)的时候,modifier 就会被使用。

我们可以点进 Box 的源码,跟踪参数 modifier 在哪个地方被使用。

调用栈-modifier.jpg
fun Composer.materialize(modifier: Modifier): Modifier {
    if (modifier.all { it !is ComposedModifier }) {
        return modifier
    }
    ...

    val result = modifier.foldIn<Modifier>(Modifier) { acc, element ->
        acc.then(
            if (element is ComposedModifier) {
                @Suppress("UNCHECKED_CAST")
                val factory = element.factory as Modifier.(Composer, Int) -> Modifier
                val composedMod = factory(Modifier, this, 0)
                materialize(composedMod)
            } else {
                element
            }
        )
    }
    ...

    return result
}

materialize() 函数中,首先会判断 modifier 链是否不包含 ComposedModifier:如果整个 modifier 链都不包含 ComposedModifier,那么就直接返回 modifier 链,不做任何处理。

if (modifier.all { it !is ComposedModifier }) {
        return modifier
    }

如果 modifier 链包含 ComposedModifier,那么就使用 Modifier.foldIn() 遍历 modifier 链,将其中的 ComposedModifier 替换成它的 factory 生成的 Modifier。

val result = modifier.foldIn<Modifier>(Modifier) { acc, element ->
        acc.then(
            if (element is ComposedModifier) {
                @Suppress("UNCHECKED_CAST")
                val factory = element.factory as Modifier.(Composer, Int) -> Modifier
                val composedMod = factory(Modifier, this, 0)
                materialize(composedMod)
            } else {
                element
            }
        )
    }

遍历替换 ComposedModifier.jpg

原来 ComposedModifier 是一个延迟初始化 Modifier 的 Modifier 是这么个意思啊。在发生组合(Conpisition)的时候,才会执行 factory() 生成真正起作用的 Modifier。

用法明白了,作用也算理解了。but...应该在什么场景下使用 ComposedModifier 呢?

val modifier1 = Modifier.size(100.dp)
val modifier2 = Modifier.composed { size(100.dp) }

这两种写法好像对日常使用完全无差别啊... 什么时候初始化 Modifier,我又不关心,反正最终得到的 Modifier 链是一样的。


When to use? 什么时候使用?

的确,在日常开发中,我们并不关心 Modifier/Element 的初始化时机,前面一直在说 “ComposedModifier 是一个可以延迟初始化 Modifier 的 Modifier”,但 Compose 官方创建 ComposedModifier 的初衷,根本不是为了延迟初始化 Modifier。

ComposedModifier 的主要价值是用于实现有“状态的 Modifier”

我们看看 Modifier.composed() 方法的注释:

Declare a just-in-time composition of a Modifier that will be composed for each element it modifies. composed may be used to implement stateful modifiers that have instance-specific state for each modified element, allowing the same Modifier instance to be safely reused for multiple elements while maintaining element-specific state.
...
materialize must be called to create instance-specific modifiers if you are directly applying a Modifier to an element tree node.

大概意思就是:可用于实现有“状态的 Modifier”,能为每个 Element(Modifier) 提供特定于实例的状态,从而使同一个 Modifier 实例可安全地重复用于多个元素,同时保持特定于元素的状态。

有状态的 Modifier?什么东西?val modofier = Modifier.size(100.dp) 所创建出来的 Modifier 没有状态吗?

别急,谈起“有状态 Stateful”和“无状态 Stateless”,还得从 Composable 函数说起,观察下面两个 @Composable 函数:

@Composable
fun StatefulCounterText() {
    var count by remember { mutableIntStateOf(0) }
    Text(
        text = count.toString(),
        modifier = Modifier.clickable { count++ }
    )
}

@Composable
fun StatelessCounterText(count: Int, onClick: () -> Unit) {
    Text(
        text = count.toString(),
        modifier = Modifier.clickable(onClick = onClick)
    )
}
  • StatefulCounterText() 是无参函数,意味着它不依赖任何外部状态。由 remember() 创建的 count 变量表示它的内部状态,这种状态会在重组时被保留,因此这是一个有状态的(Stateful)Composable 函数。
  • StatelessCounterText(count: Int...) 将显示内容 count 作为参数由外部传入,这表明它依赖于外部状态,在其内部并不会管理任何状态,而是依赖于提供的参数来确定其行为,因此这被认为是一个无状态的(Stateless)Composable 函数。

话归正题,说回“无状态的 Modifier”和“有状态的 Modifier”。

val modifier = Modifier.size(100.dp)

// 相当于
val bigSize = 100.dp
val modifier = Modifier.size(bigSize)

修饰符 size(...) 的调用,需要我们显式传参,提供一个尺寸,所以这个 Modifier 的行为是依赖于外部状态 bigSize 的,因此这是一个无状态的 Modifier。

再来看一个例子,假设要用 Modifier.rotate() 来制作做无限循环的旋转动画,那么就必须要给 Modifier.rotate() 传递旋转的角度,角度就是无状态 modifier 的外部依赖

val infiniteRotationTransition = rememberInfiniteTransition()
val rotation by infiniteRotationTransition.animateFloat(
    initialValue = 0f,
    targetValue = 360f,
    animationSpec = infiniteRepeatable(
        animation = tween(2000),
        repeatMode = RepeatMode.Restart
    )
)

Box(modifier = Modifier.fillMaxSize(),
    contentAlignment = Alignment.Center) {
    Box(
        modifier = Modifier
            .size(100.dp)
            .rotate(rotation)
            .background(purple)
    )
}
无限循环旋转.gif

因为所创建的 Modifier 是无状态的,行为依赖于外部状态 rotation,如果需要在其他页面也想使用 modifier 来实现相同的旋转动画,那么就需要在其他页面也创建外部状态,然后再将外部状态传给 modifier......

val infiniteRotationTransition = rememberInfiniteTransition()
val rotation by infiniteRotationTransition.animateFloat(...)
val modifier = Modifier
    ...   
    .rotate(rotation)

这样还怎么复用啊,每用一次都要创建一遍外部状态... 有没有什么办法,可以将 Modifier 所依赖的状态封装到 Modifier 内部呢。“将状态封装到 Modifier 内部”,这不就是“有状态的 Modifier”吗?

ComposedModifier:我好像听到有人在叫我?

fun Modifier.rotating(): Modifier = composed {
    val infiniteTransition = rememberInfiniteTransition()
    val rotation by infiniteTransition.animateFloat(
        initialValue = 0f,
        targetValue = 360f,
        animationSpec = infiniteRepeatable(
            animation = tween(2000),
            repeatMode = RepeatMode.Restart
        )
    )

    rotate(rotation)
}

利用 Modifier.composed() 我们将依赖的状态 rotation 挪到了 ComposedModifier 的内部,这样我们就创建出了一个有状态的 Modifier。现在这个 Modifier 不依赖于外部状态了,那在其他地方复用时就简单多了,直接链式调用 .rotating() 就可以了。

Box(modifier = Modifier.fillMaxSize(),
    contentAlignment = Alignment.Center) {
    Box(
        modifier = Modifier
            .size(100.dp)
            .rotating()
            .background(purple)
    )
}

我们在不同的地方链式调用 .rotating() 得到 modifier 链,然后传递给可组合内容,当发生组合时,这条 modifier 链就会被使用,接着就会遍历 modifier 链,执行 ComposedModifier 里面的 factory 函数,为每个实例创建自己的依赖。


讲到这,回顾一下,ComposedModifier 是拿来干嘛的?就是用于在自定义 Modifier 时,创建“有状态的 Modifier”


Not recommended for use 不推荐使用

因为性能等原因,目前官方已经不推荐我们使用 Modifier.composed() 了,想要了解更多为什么,可以看看这个 Youtube 视频:Compose Modifiers deep dive

好家伙,废了这么多脑细胞,脑子痒痒的,好像刚明白一点,最后你告诉我不推荐使用了...

心平气和.jpg

不推荐使用,那就是有别的办法呗,对于上面的例子,我们可以这么写,这也是目前官方的推荐写法:

@Composable
fun Modifier.rotating(): Modifier {
    val infiniteTransition = rememberInfiniteTransition()
    val rotation by infiniteTransition.animateFloat(
        initialValue = 0f,
        targetValue = 360f,
        animationSpec = infiniteRepeatable(
            animation = tween(2000),
            repeatMode = RepeatMode.Restart
        )
    )

    return this then Modifier.rotate(rotation)
    // 或者写成 return rotate(rotation)
}

这种写法相当于是把壳子 ComposedModifier 换成了 CombinedModifier,弃用了 ComposedModifier,也不存在什么延迟初始化 Modifier 了,对重组的性能会比较好。