Compose:Modifier、CombinedModifier 和 ComposedModifier

在前面的章节或多或少的都有简单介绍了下什么是 Modifier 以及 Modifier 的一些简单配置,例如 Modifier.size()、Modifier.width()、Modifier.height()、Modifier.padding()、Modifier.background()、Modifier.clip() 等等。

虽然知道有很多配置,但 Modifier 还有哪些配置?怎么能更好的使用这些 Modifier?带着疑问接下来将会对 Modifier 做一个专题深入讲解。

modifier:Modifier = Modifier 的含义

如果我们要写一个最简单的 Modifer 会是怎样的?

Modifier

是的,最简单的 Modifier 就是直接写 Modifier。Modifier 本身是一个接口,但当我们用 IDE 点击定位到 Modifier 时,会直接跳到一个实现了 Modifier 的伴生对象:

@Suppress("ModifierFactoryExtensionFunction")
@Stable
interface 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 伴生对象写法的好处是,当我们直接在程序写 Modifier 时,能拿到一个最简单的实现了 Modifier 接口的 Modifier 对象(后续 Modifier 伴生对象都用 Modifier.Companion 表示)。我们用一个简单的案例说明。

假设我们想对自定义 Composable 从外部修改相关的配置,就可以将 Modifier 作为自定义 Composable 的参数传进来:

setContent {
	Custom(Modifier.size(40.dp))
}

@Composable
fun Custom(modifier: Modifier) {
	// 注意这里使用了传进来的参数,modifier 是小写
	Box(modifier.background(Color.Blue) {}
}

但如果在调用的地方我不想设置大小了,不想传参,kotlin 是可以给函数参数提供默认值的,那么默认值应该填什么呢?就是 Modifier:

setContent {
	Custom()
}

@Composable
fun Custom(modifier: Modifier = Modifier) { // 默认值 Modifier
	Box(modifier.background(Color.Blue) {}
}

所以 Modifier.Companion 一般用法就是用来作为 Modifier 函数参数的默认值使用

Modifier.Companion 另外的一个作用是想创建一个 Modifier 时会无意间用到它。该怎么理解呢?继续看代码:

// padding() 和 backgroud() 都是扩展函数
// 不断拼接配置效果
Modifier.padding(40.dp).background(Color.Blue)

Padding.kt

@Stable
fun Modifier.padding(
    horizontal: Dp = 0.dp,
    vertical: Dp = 0.dp
) = this.then( // 获取 this 拿到上一个 Modifier.Companion 对象
    PaddingModifier(
        start = horizontal,
        top = vertical,
        end = horizontal,
        bottom = vertical,
        rtlAware = true,
        inspectorInfo = debugInspectorInfo {
            name = "padding"
            properties["horizontal"] = horizontal
            properties["vertical"] = vertical
        }
    )
)

Background.kt

fun Modifier.background(
    color: Color,
    shape: Shape = RectangleShape
) = this.then( // 获取 this 拿到上一个 Modifier.Companion 对象
    Background(
        color = color,
        shape = shape,
        inspectorInfo = debugInspectorInfo {
            name = "background"
            value = color
            properties["color"] = color
            properties["shape"] = shape
        }
    )
)

Modifier.padding() 和 Modifier.background() 是 Modifier 的扩展函数,实际上在调用扩展函数之前就会无意间创建了 Modifier.Companion

还有一个小知识点是,Compose 官方建议,如果有 Modifier 作为函数参数时,建议将 Modifier 作为第一个函数参数,这是因为当有多个函数参数有多个默认值时,第一个函数参数不需要指明是什么参数名:

setContent {
	// Custom(modifier = Modifier.size(40.dp)) 
	Custom(Modifier.size(40.dp)) // 第一个参数默认值不需要指明参数名
}

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

小结一下 Modifier.Companion 的作用

  • 作为自定义 Composable 传参 Modifier 的默认值

  • 想创建一个 Modifier 时会无意间用到

then()、CombinedModifier 和 Modifier.Element

then()

我们都知道 Modifier 是对调用顺序敏感的,而且 Modifier 可以很方便的进行链式调用:

Modifier.padding(20.dp).background(Color.Green)

在程序编写 Modifier 时是创建了 Modifier.Companion,Modifier.padding() 则是在 Modifier.Companion 基础上添加 padding;background() 则是对 Modifier.padding() 返回的 Modifier 再添加 background。最终的配置效果是按顺序拼接起来的。

Modifier.padding()、Modifier.background() 这些扩展函数是怎么做到对配置拼接的?我们选取 Modifier.background() 的源码了解原理:

Background.kt

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

private class Background constructor(
    private val color: Color? = null,
    private val brush: Brush? = null,
    private val alpha: Float = 1.0f,
    private val shape: Shape,
    inspectorInfo: InspectorInfo.() -> Unit
) : DrawModifier, InspectorValueInfo(inspectorInfo) {
	...
}

DrawModifier.kt

interface DrawModifier : Modifier.Element {

    fun ContentDrawScope.draw()
}

Modifier.kt

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)
}

可以看到在 Modifier.background() 扩展函数调用了 then() 函数,在 then() 函数传了一个对象 Background,这个对象也是一个实现了 Modifier 的对象。

Modifier.kt

infix fun then(other: Modifier): Modifier =
    if (other === Modifier) this else CombinedModifier(this, other)

then() 是在 Modifier 接口里面的一个函数,它的作用是将调用这个 then() 函数的 Modifier 对象和 then() 传参进来的 Modifier 进行合并

需要注意的是,then() 函数是可以被子类重写的,比如 Modifier.Companion 就重写了:

Modifier.kt

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
    // 重写了 then() 函数,直接返回的传参的对象
    override infix fun then(other: Modifier): Modifier = other 
    override fun toString() = "Modifier"
}

Modifier.Companion 的 then() 直接返回了传参对象,也就是 Modifier.padding()、Modifier.background() 其实返回的是自己的传参,例如 Modifier.background() 就是返回的实现了 Modifier 的 Background 对象:

Modifier.background() 
等价于
Modifier.Companion.then(Background()) 
等价于
Background()

除了 Modifier.Companion 的 then() 函数是返回传参,那其他的 Modifier 是怎么合并起来的呢?在 Compose 使用的是 CombinedModifier:

Modifier.padding().then(Modifier.background()) 
等价于
CombinedModifier(Modifier.padding(), Modifier.background())

Modifier.padding()
	.then(Modifier.background())
	.then(Modifier.size())
等价于
CombinedModifier(		
	CombinedModifier(Modifier.padding(), Modifier.background()),
	Modifier.size()
)

Modifier 就是这样通过调用 then() 函数创建 CombinedModifier 将多个 Modifier 合并,一层套一层的将配置效果拼接起来

小结一下 then() 函数:

  • 当使用 Modifier.Companion 调用 then() 函数,传参的 Modifier 如果是 Modifier.Companion,直接返回的 Modifier.Companion 自身
Modifier.then(Modifier)
等价于
Modifier
  • 当使用 Modifier.Companion 调用 then() 函数,传参的 Modifier 不是 Modifier.Companion,会直接返回 then() 函数传参的 Modifier 对象
Modifier.then(Modifier.padding())
等价于
Modifier.padding()
  • 当使用其他实现了 Modifier 接口的实现类调用 then() 函数,会使用 CombinedModifier 将当前 Modifier 和 then() 传参的 Modifier 合并
Modifier.padding().then(Modifier.background())
等价于
CombinedModifier(Modifier.padding(), Modifier.background())

CombinedModifier 和 Modifier.Element

除了 Modifier.Companion 和 CombinedModifier 之外,基本上其他的 Modifier 实现类都直接或间接的实现了另一个 Modifier 子接口:Modifier.Element

Modifier.kt

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)
}

class CombinedModifier(
    private val outer: Modifier,
    private val inner: Modifier
) : Modifier {
	// 先加入的 Modifier 先应用
    override fun <R> foldIn(initial: R, operation: (R, Modifier.Element) -> R): R =
        inner.foldIn(outer.foldIn(initial, operation), operation)

	// 后加入的 Modifier 先应用
    override fun <R> foldOut(initial: R, operation: (Modifier.Element, R) -> R): R =
        outer.foldOut(inner.foldOut(initial, operation), operation)

	// 至少有一个 Modifier 符合条件就返回 true
    override fun any(predicate: (Modifier.Element) -> Boolean): Boolean =
        outer.any(predicate) || inner.any(predicate)

	// 所有 Modifier 符合条件就返回 true
    override fun all(predicate: (Modifier.Element) -> Boolean): Boolean =
        outer.all(predicate) && inner.all(predicate)

    override fun equals(other: Any?): Boolean =
        other is CombinedModifier && outer == other.outer && inner == other.inner

    override fun hashCode(): Int = outer.hashCode() + 31 * inner.hashCode()

    override fun toString() = "[" + foldIn("") { acc, element ->
        if (acc.isEmpty()) element.toString() else "$acc, $element"
    } + "]"
}

foldIn()、foldOut()、any() 和 all() 这几个函数并不是用来做通用功能的,而是主要为了 CombinedModifier 这个类而创造的

  • any():在 Modifier.Element 它是传参一个条件判断,直接将自身返回,是为了查看 CombinedModifier 是否至少有一个符合条件的,也就是 CombinedModifier 的 inner 和 outer 两个参数,包括 inner 或 outer 内的 CombinedModifier

  • all():查看 CombinedModifier 是否所有都符合条件

其他 Modifier 内部没有包含其他 Modifier,而为了能配合 CombinedModifier 的遍历,所以其他 Modifier 都实现了 Modifier.Element 提供的默认处理

foldIn() 和 foldOut() 也是为了配合 CombinedModifier 的遍历。

  • foldIn():先加入的 Modifier 先应用

  • foldOut():展开来后加入的 Modifier 先应用

Modifier.Companion 也有实现这几个函数,但它的返回都在提示你:请忽略我。Modifier.Companion 相当于一张白纸,作为最简单最基本的 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"	
}

接下来对 CombinedModifier 和 Modifier.Element 做一个小结:

  • CombinedModifier 是为了创建 Modifier 链条的、没有实际效果的 Modifier

  • Modifier.Element 是除了 Modifier.Companion 和 CombinedModifier 外,其他 Modifier 直接或间接的父接口,它只是为了配合 CombinedModifier 使用,而它自身并没有什么作用

Modifier.composed() 和 ComposedModifier

ComposedModifier.kt

private open class ComposedModifier(
	inspectorInfo: InspectorInfo.() -> Unit,
	val factory: @Composable Modifier.() -> Modifier
) : Modifier.Element, InspectorValueInfo(inspectorInfo)

ComposedModifier 是私有的它并不允许直接创建,看源码也能发现它实现了 Modifier.Element,本身并没有任何功能实现。

ComposedModifier 的作用是通过 factory 工厂函数在组合过程时执行 factory 创建出 Modifier 并使用

创建 ComposedModifier 需要使用 Modifier.composed() 扩展函数:

ComposedModifier.kt

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

// 使用 Modifier.composed()
setContent {
	Box(Modifier.composed { Modifier.padding(8.dp) })
}

正常情况下当程序执行到例如 Modifier.padding() 时就会直接创建 Modifier,而 Modifier.composed() 需要在 lambda 返回一个 Modifier,程序执行到 lambda 时并不会立即创建 Modifier,而是将提供的 Modifier 存到 ComposedModifier 的工厂函数里,等到组合过程再创建

那到底会在什么时候执行工厂函数?我们深入到 Box() 源码跟踪 Modifier 被调用的地方:

Box.kt

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

Layout.kt

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

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

ComposedModifier.kt

@Suppress("ModifierFactoryExtensionFunction")
fun Composer.materialize(modifier: Modifier): Modifier {
    ...

	// foldIn() 将所有 Modifier 按顺序遍历
	// 执行所有 ComposedModifier 的工厂函数并返回 Modifier,否则返回自身
	// 最终重新用 then() 再次将所有 Modifier 拼接
    val result = modifier.foldIn<Modifier>(Modifier) { acc, element ->
        acc.then(
        	// 如果是 ComposedModifier,就执行 factory 工厂函数获取 Modifier
            if (element is ComposedModifier) {
                @kotlin.Suppress("UNCHECKED_CAST")
                val factory = element.factory as Modifier.(Composer, Int) -> Modifier
                val composedMod = factory(Modifier, this, 0)
                
                // 递归将所有 ComposedModifier 的工厂函数都执行
                materialize(composedMod) 
            } else element
        )
    }

    endReplaceableGroup()
    return result
}

从源码可以查看到 Modifier.composed() 确实会在组合过程中对所有 Modifier 遍历,查找 ComposedModifier 并执行工厂函数返回的 Modifier,最后再次将所有 Modifier 使用 then() 拼接起来。

到这里就会有疑问了:ComposedModifier 这么做有什么作用?这和直接写 Modifier 有啥区别?

这个答案可以在 Modifier.composed 的注释里找到:

在这里插入图片描述

注释中提到了几个关键词:stateful modifiers 有状态的 Modifier,reused 重用,element-specific state 状态独有

该怎么理解这几个词的意思呢?还是看代码:

setContent {
	val modifier = Modifier.composed { Modifier.padding(8.dp) }
	// val modifier = Modifier.padding(8.dp)
	Box(modifier)
	Text("vincent", modifier)
}

Modifier.padding() 和 Modifier.composed() 唯一的区别是,Modifier.composed() 修改了内部 lambda 创建 Modifier 的时间节点,但是对显示结果而言它们二者是没有任何区别的

实际上 Modifier.composed() 不是用在案例写的那种场景下,而是用在有状态的 Modifier 的场景。

什么叫有状态的 Modifier?按上面的例子 8.dp 就是一个状态,不过因为在这里已经写死了数值所以是无状态的,所以也不是 Modifier.composed() 的使用场景。

setContent {
	val modifier = Modifier.composed { 
		var padding by rememeber { mutableStateOf(8.dp) }
	  	Modifier.padding(padding).clickable { padding = 0.dp }
	}
	Box(modifier)
	Text("vincent", modifier)
}

代码中我们将 8.dp 另外提取了出来为一个变量 padding,这时候 padding 就是一个状态,此时 modifier 它就是有状态的,而且每个使用 modifier 的 Composable 的状态都是独立的

我们可以用一个演示代码验证 Modifier 是否为独立状态:

setContent {
    var padding by remember { mutableStateOf(8.dp) }
    val modifier = Modifier.padding(padding).clickable { padding = 0.dp }
    Column {
    	// Box 和 Text 共用同一个 padding 状态
    	// 点击任意一个 Composable,二者的内边距都会被修改为 0dp
        Box(Modifier.background(Color.Blue) then modifier)
        Text("vincent", Modifier.background(Color.Green) then modifier)
    }
}

在这里插入图片描述

setContent {
	val modifier = Modifier.composed { 
		var padding by rememeber { mutableStateOf(8.dp) }
  		Modifier.padding(padding).clickable { padding = 0.dp }
  	}
  	Column {
  		// Box 和 Text 使用 Modifier.composed() 
  		// 因为状态独立,点击不同的 Composable 只会影响对应 Composable 的内边距
		Box(Modifier.background(Color.Blue) then modifier)
		Text("vincent", Modifier.background(Color.Green) then modifier)
	}
}

在这里插入图片描述

可以发现使用 Modifier.composed() 返回的 Modifier 分别设置给不同的 Composable,点击 Composable 后蓝色方块并没有消失,这说明 Box() 和 Text() 使用的不同的 Modifier 状态,所以不会互相影响。

不过这个验证还是不能说明 Modifier.composed() 的作用和具体使用场景,因为我们想要实现 Modifier.composed() 分别有独立的状态,我们一般会这么写,反而能避免问题出现:

setContent {
    var padding1 by remember { mutableStateOf(8.dp) }
    val modifier1 = Modifier.padding(padding1).clickable { padding1 = 0.dp }
    
    var padding2 by remember { mutableStateOf(8.dp) }
    val modifier2 = Modifier.padding(padding2).clickable { padding2 = 0.dp }
    
    Column {
    	// Box 和 Text 分别使用不同的 modifier,修改不同的状态
        Box(Modifier.background(Color.Blue) then modifier1)
        Text("vincent", Modifier.background(Color.Green) then modifier2)
    }
}

实际上 Modifier.composed() 的使用场景是,当我们需要创建的 Modifier 需要给它添加一些内部状态,这时候我们需要使用 Modifier.composed() 来为它提供一个 Composable 的上下文环境,从而让我们可以使用 remember;最终目的是让 Modifier 要能使用 remember 具备内部状态。而且因为 Modifier.composed() 是使用工厂函数延迟创建的,每调用一次就创建一次,所以也具备了每个 Composable 调用它时状态都是独立的

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

因为 Modifier.composed() 的工厂函数提供了 Composable 上下文环境,所以没有对状态有需求,它也能使用在需要 Composable 环境的 Modifier 的创建。比如 LaunchedEffect、CompositionLocal 等:

fun Modifier.coroutineModifier() = composed {
	LaunchedEffect(...) {
		...
	}
	...
	Modifier
}

fun Modifier.localModifier() = composed {
	LocalContext.current
	Modifier
}

不过 大多数时候 Modifier.composed() 都会用在需要内部状态使用 remember 的场景

简单总结下 ComposedModifier 或 Modifier.composed():

  • 按照官方注释的说法,Modifier.composed() 能创建出带有状态的 Modifier,让这个 Modifier 在多处被重用

  • 有状态的 Modifier 就是被 remember 包着的变量和 Modifier 放在一起作为它的内部状态使用

  • 重用就是 Modifier.composed() 在每一个使用的地方都创建一个内部状态独立的 Modifier,而不会互相影响

  • Modifier.composed() 不仅能提供内部状态,在一些需要 Composable 上下文环境例如 LaunchedEffect 或 CompositionLocal 等地方使用 Modifier,也可以使用它

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值