Modifier
modifier: Modifier = Modifier的含义
在调用各种Composable函数时,对于它的modifier参数,我们通常会填写 Modifier.Xxx,比如:
kotlin
Box(
modifier = Modifier
.padding(8.dp)
.background(Color.Green)
)
那么这个Modifier是什么呢?点进去看看:
kotlin
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接口。
单例对象的声明只需使用object
关键字,而加上一个companion
关键字,就变为了伴生对象,它可以让我使用这个单例对象实现的接口名------Modifier
,去直接获取这个伴生对象,而不需要通过类名.实例名的方式。
它有一个完整的写法:Modifier.Companion
,但通常我们可以直接使用Modifier
来引用这个伴生对象。
伴生对象在Kotlin中常常用来实现类似Java中静态方法和静态属性的功能
那我们拿到一个实现了Modifier接口的伴生对象,作用是什么?
1. 作为修饰符链的起点
当你写 Modifier
时,实际上是获取了这个伴生对象的实例,它是一个最简单的实现了 Modifier 接口的对象,它的实现非常简单,基本上是一个"空修饰符",主要作用是开始链式调用。
kotlin
// 使用 Modifier 单例对象作为起点
val modifier = Modifier
.padding(12.dp)
.background(Color.Green)
.padding(16.dp)
// 更多修饰符...
2. 提供空修饰符功能
Modifier 单例对象本身不执行任何修饰操作,它相当于一个"空修饰符",可以在不需要任何修饰时使用。比如在函数中,modifier参数的默认值通常都是Modifier。
kotlin
Text(
text = "Hello World",
modifier = Modifier // 不添加任何修饰
)
3. 提供修饰符扩展函数的接收者
所有的修饰符扩展函数都是定义在 Modifier 接口上的,而 Modifier 单例对象作为这个接口的实现,也当然可以调用这些扩展函数。这使得我们可以方便地使用各种修饰符。
kotlin
// Modifier 扩展函数示例
fun Modifier.customPadding(): Modifier {
return this.padding(horizontal = 16.dp, vertical = 8.dp)
}
// 使用自定义扩展函数
Box(modifier = Modifier.customPadding())
我们现在回过头来看看 modifier: Modifier = Modifier
的含义:
kotlin
@Composable
fun MyComponent(modifier: Modifier = Modifier) {
// 组件实现
}
- modifier: 函数的参数名
- 第一个Modifierr: 参数的类型,表明了这个参数是实现了Modifier接口的对象
- 第二个Modifier: 参数的默认值,是Modifier 单例对象的实例,代表是一个空的修饰符
官方建议:将modifier参数作为第一个有默认值的参数
因为在Kotlin中,第一个有默认值的参数有一个特权:填写参数时,可以不用写参数名,而后续的参数则必须使用命名参数的形式。
这样可以使代码更简洁。
比如下面这样:
kotlin
@Composable
fun CustomText(text: String, modifier: Modifier = Modifier, color: Color = Color.Black) {
Text(text = text, modifier = modifier, color = color)
}
// 正常调用
CustomText(text = "Hello World!")
// 特权:可以不用写modifier = Modifier
CustomText("Charlie", Modifier)
// 跳过中间参数,必须使用命名参数
CustomText("David", color = Color.Cyan)
then() & CombinedModifie
我们写了Modifier.background(Color.Green)
这行代码时,背后发生了什么?
点进去background()
函数的源码:
kotlin
// Background.kt
fun Modifier.background(
color: Color,
shape: Shape = RectangleShape
): Modifier {
// ..
return this.then(
BackgroundElement(
// ..
)
)
}
this
是调用者,在这里是Modifier
伴生对象实例,它调用了then()方法:
kotlin
// Modifier.kt
infix fun then(other: Modifier): Modifier =
if (other === Modifier) this else CombinedModifier(this, other)
other
参数的类型是Modifier,而我们传入了一个BackgroundElement类型的对象,这说明它是实现了Modifier接口的,点进去查看一下它的继承关系:
kotlin
private class BackgroundElement(
// ...
) : ModifierNodeElement<BackgroundNode>() { // 点击ModifierNodeElement
// ...
}
kotlin
abstract class ModifierNodeElement<N : Modifier.Node> : Modifier.Element, InspectableValue { // 点击Element
// ...
}
kotlin
interface Element : Modifier {
// ...
}
果然,确实是Modifier接口的实现类。
那么这个then()
方法做了些什么呢?
我现在就可以告诉你,将两个修饰符组合成一个新的修饰符链。
比如在当前的示例中,它会将当前修饰符(Modifier
)与新创建的 BackgroundElement
修饰符连接起来,形成一个新的修饰符链。
具体的工作原理,我们进入then方法的源码中看一下,不过不要直接点进去,因为当前的调用者Modifier
伴生对象对then()
方法进行了重写:
kotlin
companion object : Modifier {
// ...
override infix fun then(other: Modifier): Modifier = other
override fun toString() = "Modifier"
}
发现它直接返回了参数,所以此次调用中返回的就是参数里面的Modifier, 即BackgroundElement
。这是因为 Modifier
伴生对象代表空修饰符,与任何修饰符组合时都应该返回那个修饰符本身。
再来看看通用的then()
方法:如果other
参数是Modifier
伴生对象,就返回自身;否则,就创建CombinedModifier对象,将当前修饰符 (this
) 和新修饰符 (other
)组合,再返回。
kotlin
// Modifier.kt
infix fun then(other: Modifier): Modifier =
if (other === Modifier) this else CombinedModifier(this, other)
CombinedModifier是一个Modifier的实现类,作用是将两个修饰符组合成一个单一的修饰符。
kotlin
class CombinedModifier(
internal val outer: Modifier,
internal val inner: Modifier
) : Modifier {
// ..
}
其中有两个关键字段:
outer
:外部修饰符(链中较早添加的修饰符)inner
:内部修饰符(链中较晚添加的修饰符)
这种结构形成了一个嵌套的链表,使得多个修饰符可以按特定顺序应用。
例如下面两个调用是等价的:
kotlin
Modifier
.background(Color.Green)
.then(Modifier.padding(8.dp))
.then(Modifier.size(16.dp))
// ============================
CombinedModifier(
CombinedModifier(Modifier.background(Color.Green), Modifier.padding(8.dp)),
Modifier.size(16.dp)
)
形成的嵌套链表是:
通过这种结构,Compose 运行时能够按正确的顺序应用每个修饰符的效果。
小结:CombinedModifier的主要作用是将多个独立的修饰符(Modifier.Element)组合成一个连贯的修饰符链。
Modifier.Element
大部分真实有效果的Modifier都直接或间接实现了Element接口,就比如前面的BackgroundElement
。
kotlin
// 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)
}
Element接口的这些方法,虽然继承于Modifier接口,但是它们不是用来做通用功能的,而是为了CombinedModifier这个类而创建的,使得修饰符链能够正常工作的。
我们先来看看这些函数的功能。
all() & any()
any
方法用于检查Modifier链中,是否存在至少一个Modifier节点满足条件,存在则返回true。
如果节点类型是 Element
,它直接对自身进行判断;如果是 CombinedModifier
,它会递归地检查整个Modifier链。
kotlin
// Element 中的实现
override fun any(predicate: (Element) -> Boolean): Boolean = predicate(this)
// CombinedModifier 中的实现
override fun any(predicate: (Modifier.Element) -> Boolean): Boolean =
outer.any(predicate) || inner.any(predicate)
all
函数:也是差不多,只不过要所有Modifier节点满足条件,才会返回true。
kotlin
// Element 中的实现
override fun all(predicate: (Element) -> Boolean): Boolean = predicate(this)
// CombinedModifier 中的实现
override fun all(predicate: (Modifier.Element) -> Boolean): Boolean =
outer.all(predicate) && inner.all(predicate)
并且我们再来看看Modifier伴生对象的any()
和 all()
方法实现,发现它的实现并不会影响到最终结果,any 始终返回 false,all 始终返回 true。进一步证实了Modifier伴生对象是一个"透明人"。
kotlin
companion object : Modifier {
override fun any(predicate: (Element) -> Boolean): Boolean = false
override fun all(predicate: (Element) -> Boolean): Boolean = true
// ..
}
foldIn() & foldOut()
foldIn()
、foldOut()
是为了Modifier链的遍历存在的,特别是它们在 CombinedModifier 中的实现,因为CombinedModifier 需要递归地处理嵌套的Modifier结构。
foldIn
方法是从外到内遍历Modifier链,对每个元素应用给定的操作。
kotlin
// Element 中的实现
override fun <R> foldIn(initial: R, operation: (R, Element) -> R): R =
operation(initial, this)
// CombinedModifier 中的实现
override fun <R> foldIn(initial: R, operation: (R, Modifier.Element) -> R): R =
inner.foldIn(outer.foldIn(initial, operation), operation)
在 CombinedModifier
的实现中,foldIn
首先对 outer
应用操作,然后将结果传递给 inner
的 foldIn
方法。
与之相对的foldOut
方法是从内到外遍历修饰符链,主要用于绘制阶段。
kotlin
// Element 中的实现
override fun <R> foldOut(initial: R, operation: (Element, R) -> R): R =
operation(this, initial)
// CombinedModifier 中的实现
override fun <R> foldOut(initial: R, operation: (Modifier.Element, R) -> R): R =
outer.foldOut(inner.foldOut(initial, operation), operation)
在 CombinedModifier
的实现中,foldOut
首先对 inner
应用操作,然后将结果传递给 outer
的 foldOut
方法,确保了从内到外的遍历顺序。
foldIn 和 foldOut 方法的作用不只是遍历Modifier链,主要应用场景是 Compose 的渲染流程:
- foldIn:在测量和布局阶段,修饰符需要从外到内应用。例如,先应用外层的 padding,再应用内层的 size 约束。
- foldOut:在绘制阶段,修饰符需要从内到外应用。例如,先绘制内容,再绘制背景,最后绘制边框。
总结
CombinedModifier 的作用是创建Modifier链式结构。
在这种链式结构下,我们需要去遍历Modifier链和查询符合某种条件的Modifier节点,所以就有了any
和 all
方法,实现了对Modifier链的条件匹配功能,分别用于检测是否存在满足条件的Modifier元素以及是否所有元素均满足特定条件;
而 foldIn
和 foldOut
方法则提供了正序、逆序遍历,分别实现从外到内(用于测量和布局阶段)和从内到外(用于绘制阶段)的有序处理过程。