Jetpack Compose——interface Modifier

Modifier 接口

modifier: Modifier = Modifier

自定义 Composable 函数时,通常会将 modifier 参数作为第一个可选参数。这样,调用者就可以使用 Modifier 对象来修改组合内容的行为和外观。

kotlin 复制代码
@Composable 
fun CustomCard(
    modifier: Modifier = Modifier, 
    content: @Composable () -> Unit
) { ... }

modifier: Modifier = Modifier 里 3 个单词都一样,分别都是什么意思呢?

  • modifier 最左边的 modifier 是参数名;
  • : Modifier 冒号右边的 Modifier 是参数类型,代表参数类型是 Modifier 接口的实现类;
  • = Modifier 等号右边的 Modifier 是参数默认值,这里的 Modifier 是一个实现了 Modifier 接口,但没有没有实际作用的 Modifier 单例对象
kotlin 复制代码
interface Modifier {
    ...
    companion object : Modifier {
        ...
    }
}

不得不感叹 Kotlin 的语法糖 🍬,我们使用 companion object 一般是为了定义某些静态字段或方法。Compose 居然在 Modifier 接口里面使用 companion object 且实现了 Modifier 接口,这样一来,Modifier 这个名字就既可以用来表示 Modifier 接口,也可以用来表示那个无实际作用的 Modifier 单例对象(完整写法是 Modifier.Companion)。

then() & CombinedModifier

当你写出这样一行代码:Modifier.size(100.dp),背后发生了什么呢?点进源码跟一下:

kotlin 复制代码
fun Modifier.size(size: Dp) = this.then(
    SizeElement(...)
)

能看到 size() 是 Modifier 接口的一个扩展函数,它直接调用了 Modifier 接口的 then() 函数,传入了一个 SizeElement 对象(SizeElement 也是 Modifier 接口的实现类)。

kotlin 复制代码
interface Modifier {
    ...
    infix fun then(other: Modifier): Modifier =
        if (other === Modifier) this else CombinedModifier(this, other)
        // 如果传入的参数是无实际作用的 Modifier 单例对象,那么直接返回自己,否则利用 CombinedModifier 合并两个 Modifier: 自身 & 参数
}

class CombinedModifier(...) : Modifier { ... }

then() 函数的作用是将两个 Modifier 合并成一个 Modifier。

具体到上面的例子,合并的双方,一个是无实际作用的 Modifier 单例对象,另一个是 SizeElement 对象。二者合并后是一个 CombinedModifier 对象,这个 CombinedModifier 类也是 Modifier 接口的实现类。

kotlin 复制代码
class CombinedModifier(
    internal val outer: Modifier,
    internal val inner: Modifier
) : Modifier { ... }

此时如果在 Modifier.size(100.dp) 的后面再继续调用 .padding(10.dp),那么就会再次调用 then() 函数,传入一个 PaddingElement 对象。由 Modifier.size(100.dp) 得到的 CombinedModifier 对象,和新创建的 PaddingElement 对象,会被合并成一个新的 CombinedModifier 对象。

原来 Modifier 的链式调用,就是不断调用 Modifier.then() 函数,利用 CombinedModifier 对 Modifier 进行合并的过程啊。

Element

上面已经提到,调用 Modifier.size()Modifier.padding() 过程中会分别创建出一个 SizeElement 对象和一个 PaddingElement 对象。

其实 SizeElement 和 PaddingElement 都是通过实现 Element 接口来间接实现 Modifier 接口的。

kotlin 复制代码
interface Modifier {
    ...
    companion object : Modifier {
        ...
    }

    interface Element : Modifier {
        ...
    }
}

class CombinedModifier(
    internal val outer: Modifier,
    internal val inner: Modifier
) : Modifier { ... }

在 Compose 里面,Modifier 接口的子类,除了无实际作用的 Modifier 单例对象,和用于合并 Modifier 的 CombinedModifier 类,其他的,拥有实际作用的 Modifier 类,几乎都是通过实现 Element 接口来间接实现 Modifier 接口的。

foldIn() / foldOut() & all() / any()

kotlin 复制代码
interface Modifier {
    fun <R> foldIn(initial: R, operation: (R, Element) -> R): R
    fun <R> foldOut(initial: R, operation: (Element, R) -> R): R
    fun any(predicate: (Element) -> Boolean): Boolean
    fun all(predicate: (Element) -> Boolean): Boolean
    ...
}

Modifier 接口里面有这四个函数,它们的作用是什么呢?

all() & any()

提起 all()any(),大家应该都知道 Kotlin 集合里面,它们是用来判断集合里面的元素是否全部满足某个条件,或者是否有任意一个元素满足某个条件的。

kotlin 复制代码
val list = listOf(1, 1, 2)
val allOne = list.all { it == 1 }
val anyTwo = list.any { it == 2 }

而在 Modifier 里面,all()any() 的作用也是类似的。前面已经了解到,Modifier 链式调用后,会得到一个类似于二叉树的链表结构,每一个节点都是一个 Modifier,它的实际类型既可能是一个具有实际作用的 Modifier(Element),也可能是一个无实际作用的 Modifier 单例对象,还可能是一个专门用于合并 Modifier 的 CombinedModifier。

kotlin 复制代码
interface Modifier {
    ...
    fun all(predicate: (Element) -> Boolean): Boolean
    fun any(predicate: (Element) -> Boolean): Boolean
}

all() / any() 的作用,就是判断某个 Modifier 节点下,所包含的所有 Element 是否都满足某个条件,或者是否有任意一个 Element 满足某个条件的。可以看到 all()any() 的参数就是判断的条件,都是函数类型,接收一个 Element,判断条件后返回 Boolean。

kotlin 复制代码
class CombinedModifier(
    internal val outer: Modifier,
    internal val inner: Modifier
) : Modifier { 
    ...
    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 的内部直接包含两个 Modifier,所以 CombinedModifier 的 any()all() 函数的实现,就是拿着参数(判断条件),转发调用两个直接子节点的 any()all() 函数,并将得到的结果合并返回。 这样的实现很合理吧,如果要判断某个节点下面的所有 Element 是否都满足某个条件,是不是得对该节点所分叉出来的两个子节点进行同样的判断,然后将两个结果进行合并。

kotlin 复制代码
interface Element : Modifier {
    ...
    override fun any(predicate: (Element) -> Boolean): Boolean = predicate(this)
    override fun all(predicate: (Element) -> Boolean): Boolean = predicate(this)
}

而 CombinedModifier 直接包含的两个 Modifier,它们的实际类型可能是 Element 或 CombinedModifier。 如果是 Element,那么就直接对自己应用判断条件;如果是 CombinedModifier,那么继续递归转发调用 any()all() 函数。

kotlin 复制代码
companion object : Modifier {
    override fun any(predicate: (Element) -> Boolean): Boolean = false
    override fun all(predicate: (Element) -> Boolean): Boolean = true
}

当然,递归到最深层的那个 CombinedModifier,它所包含的两个 Modifier,其中一个是无实际作用的 Modifier 单例对象,它是妥妥的透明人,对这个递归判断过程没有任何影响,是可以忽略的。所以它的 any() 直接返回 falseall() 直接返回 true

foldIn() & foldOut()

除了 all()any(),Modifier 里面还有另外两个函数 foldIn()foldOut(), 要理解它们,首先要理解 Kotlin 里面集合的 fold() 函数。

kotlin 复制代码
data class Person(val name: String, val height: Int)

fun main() {
    val babyMike = Person(name = "Mike", height = 53)
    
    // 从出生到 18 岁,每年身高增长的厘米
    val heightGrowthEveryYear = listOf(25, 12, 8, 7, 7, 7, 6, 7, 5, 6, 7, 8, 8, 6, 3, 1, 1, 1)
    
    val adultMike = heightGrowthEveryYear.fold(babyMike) { mike, growth ->
        mike.copy(height = mike.height + growth)
    }
    
    println("Mike 出生时的身高:${babyMike.height} cm")
    println("Mike 18 岁的身高:${adultMike.height} cm")
}

Kotlin 集合里面的 fold() 函数的作用是将集合里面的元素,折叠转换成一个新的值。它接收两个参数,第一个参数是初始值,第二个参数是一个函数,这个函数接收两个参数,第一个参数是上一次折叠的结果,第二个参数是集合里面的元素,返回值是折叠后的结果。

具体到上面的例子:

  • 第一次计算:将初始值 babyMike(身高 53 cm)和第一个元素 25 (第一年增长的身高)传入函数,计算得到一岁的 Mike,身高 53 + 25 = 78 cm;

  • 第二次计算:将第一次计算的结果(一岁的 Mike,身高 78 cm)和第二个元素 12 (第二年增长的身高)传入函数,计算得到两岁的 Mike,身高 78 + 12 = 90 cm;

  • 依次类推,最后得到的结果是 adultMike,18 岁的 Mike,身高 178 cm。

如此一来,fold() 函数就将集合里面的所有元素(每年增长的身高,类型是 Int)折叠成一个新的值(18 岁的 Mike,类型和传入的初始值 babyMike 一样,都是 Person)。

就好像滚雪球一样,给定一个小雪球(初始值),然后不断滚动,每次滚动都会增加雪球的体积(集合里面的元素),最后得到一个大雪球(折叠后的结果)。

将视线转回 Modifier 接口里的 foldIn() / foldOut(),作用就是对 Modifier 链式调用后得到的 Modifier 二叉树里面的所有 Element 进行折叠转换。

可是为什么还要分成 foldIn() 和 foldOut() 两个函数呢?不是有一个 fold() 函数就够了吗?别忘了 Modifier 是顺序敏感的!正向遍历折叠,和反向遍历折叠,得到的最终结果可能并不一样。

前面我们已经知道,Modifier.size(100.dp) 相当于 Modifier then SizeElement(...),then 会将两个 Modifier 对象进行合并,得到的一个 CombinedModifier 对象。其实所谓的合并很简单,CombinedModifier 内部会使用变量 outerinner 分别保存需要合并的两个 Modifier 对象的引用地址。

kotlin 复制代码
interface Modifier {
    ...
    infix fun then(other: Modifier): Modifier =
        if (other === Modifier) this else CombinedModifier(this, other)
}

class CombinedModifier(
    internal val outer: Modifier,
    internal val inner: Modifier
) : Modifier { ... }

foldOut()

代码 Modifier.size(100.dp).padding(10.dp) 相当于 Modifier then SizeElement then PaddingElement,对应的 Modifier 二叉树如下:

如果使用 foldOut() 对根节点所包含的所有 Element 进行折叠转换,那么就是从下往上,优先折叠 inner 节点:inner.foldOut(initial, operation),将其折叠后的结果作为 initial 参数,再折叠 outer 节点:outer.foldOut(...)

在我们的例子里面,根节点的 inner 节点是一个 Element,Element 的 foldOut() 函数的实现,是直接调用 operation() 函数,将自己作为参数传入,然后返回 operation() 函数的返回值,也就是直接对自己进行折叠转换,因为它自身就是一个 Element 嘛。

kotlin 复制代码
interface Element : Modifier {
    override fun <R> foldOut(initial: R, operation: (Element, R) -> R): R =
    	operation(this, initial)
}

根节点的 inner 节点折叠的结果,将作为折叠 outer 的初始值,如此循环往复,形成闭环,最终得到折叠转换的结果。

无实际作用的 Modifier 单例对象,它的 foldOut() 实现会直接返回 initial 参数,因为它是无实际作用的,折叠与它无关。

kotlin 复制代码
companion object : Modifier {
    override fun <R> foldOut(initial: R, operation: (Element, R) -> R): R = initial
}

foldIn()

foldIn()foldOut() 的区别就是,foldIn() 是从节点开始,优先折叠 outer 节点,再折叠 inner 节点,过程就不再赘述了。

END.

了解 Modifier 里面的 then()CombinedModifier,能够帮助我们更好地理解 Modifier 链式调用的工作原理。all()any()foldOut()foldInt() 这些函数,则让我们窥探到 Modifier 链背后的执行逻辑。

相关推荐
太空漫步112 小时前
android社畜模拟器
android
海绵宝宝_4 小时前
【HarmonyOS NEXT】获取正式应用签名证书的签名信息
android·前端·华为·harmonyos·鸿蒙·鸿蒙应用开发
凯文的内存6 小时前
android 定制mtp连接外设的设备名称
android·media·mtp·mtpserver
天若子6 小时前
Android今日头条的屏幕适配方案
android
林的快手8 小时前
伪类选择器
android·前端·css·chrome·ajax·html·json
望佑8 小时前
Tmp detached view should be removed from RecyclerView before it can be recycled
android
xvch10 小时前
Kotlin 2.1.0 入门教程(二十四)泛型、泛型约束、绝对非空类型、下划线运算符
android·kotlin
人民的石头14 小时前
Android系统开发 给system/app传包报错
android
yujunlong391914 小时前
android,flutter 混合开发,通信,传参
android·flutter·混合开发·enginegroup
rkmhr_sef15 小时前
万字详解 MySQL MGR 高可用集群搭建
android·mysql·adb