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

相关推荐
我命由我123453 小时前
Android 对话框 - 对话框全屏显示(设置 Window 属性、使用自定义样式、继承 DialogFragment 实现、继承 Dialog 实现)
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
Jeled6 小时前
Android 本地存储方案深度解析:SharedPreferences、DataStore、MMKV 全面对比
android·前端·缓存·kotlin·android studio·android jetpack
我命由我1234518 小时前
Android 开发问题:getLeft、getRight、getTop、getBottom 方法返回的值都为 0
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
alexhilton6 天前
Kotlin互斥锁(Mutex):协程的线程安全守护神
android·kotlin·android jetpack
是六一啊i7 天前
Compose 在Row、Column上使用focusRestorer修饰符失效原因
android jetpack
用户060905255228 天前
Compose 主题 MaterialTheme
android jetpack
用户060905255228 天前
Compose 简介和基础使用
android jetpack
用户060905255228 天前
Compose 重组优化
android jetpack
行墨8 天前
Jetpack Compose 深入浅出(一)——预览 @Preview
android jetpack