Modifier接口

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 应用操作,然后将结果传递给 innerfoldIn 方法。

与之相对的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 应用操作,然后将结果传递给 outerfoldOut 方法,确保了从内到外的遍历顺序。

foldIn 和 foldOut 方法的作用不只是遍历Modifier链,主要应用场景是 Compose 的渲染流程:

  • foldIn:在测量和布局阶段,修饰符需要从外到内应用。例如,先应用外层的 padding,再应用内层的 size 约束。
  • foldOut:在绘制阶段,修饰符需要从内到外应用。例如,先绘制内容,再绘制背景,最后绘制边框。

总结

CombinedModifier 的作用是创建Modifier链式结构。

在这种链式结构下,我们需要去遍历Modifier链和查询符合某种条件的Modifier节点,所以就有了anyall 方法,实现了对Modifier链的条件匹配功能,分别用于检测是否存在满足条件的Modifier元素以及是否所有元素均满足特定条件;

foldInfoldOut 方法则提供了正序、逆序遍历,分别实现从外到内(用于测量和布局阶段)和从内到外(用于绘制阶段)的有序处理过程。

相关推荐
我命由我1234512 天前
Android 解绑服务问题:java.lang.IllegalArgumentException: Service not registered
android·java·开发语言·java-ee·安卓·android jetpack·android-studio
我命由我1234513 天前
MQTT - Android MQTT 编码实战(MQTT 客户端创建、MQTT 客户端事件、MQTT 客户端连接配置、MQTT 客户端主题)
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
前行的小黑炭14 天前
Android LiveData源码分析:为什么他刷新数据比Handler好,能更节省资源,解决内存泄漏的隐患;
android·kotlin·android jetpack
_一条咸鱼_14 天前
深度剖析:Java PriorityQueue 使用原理大揭秘
android·面试·android jetpack
_一条咸鱼_14 天前
揭秘 Java PriorityBlockingQueue:从源码洞悉其使用原理
android·面试·android jetpack
_一条咸鱼_14 天前
深度揭秘:Java LinkedList 源码级使用原理剖析
android·面试·android jetpack
_一条咸鱼_14 天前
深入剖析 Java LinkedBlockingQueue:源码级别的全面解读
android·面试·android jetpack
_一条咸鱼_14 天前
探秘 Java DelayQueue:源码级剖析其使用原理
android·面试·android jetpack
_一条咸鱼_14 天前
揭秘 Java ArrayDeque:从源码到原理的深度剖析
android·面试·android jetpack
_一条咸鱼_14 天前
深入剖析!Android WebView使用原理全解析:从源码底层到实战应用
android·面试·android jetpack