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()
直接返回 false
,all()
直接返回 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 内部会使用变量 outer
和 inner
分别保存需要合并的两个 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 链背后的执行逻辑。