深度解析 Compose 的 Modifier 原理 -- Modifier、CombinedModifier

发布时间:2024年01月19日

在这里插入图片描述


" Jetpack Compose - - Modifier 系列文章 "


????📑 《 深入解析 Compose 的 Modifier 原理 - - Modifier、CombinedModifier 》

????📑 《 深度解析 Compose 的 Modifier 原理 - - Modifier.composed()、ComposedModifier 》

????📑 《 深入解析 Compose 的 Modifier 原理 - - Modifier.layout()、LayoutModifier 》

????📑 《 深度解析 Compose 的 Modifier 原理 - - DrawModifier 》

????📑 《 深度解析 Compose 的 Modifier 原理 - - PointerInputModifier 》

????📑 《 深度解析 Compose 的 Modifier 原理 - - ParentDataModifier 》


众所周知:原理性分析的文章,真的很难讲的通俗易懂,讲的简单了就没必要写了,讲的繁琐难懂往往大家也不乐意看,所以只能尽量找个好的角度(比如从 Demo 代码示例出发)慢慢带着大家去钻源码,如果确实能帮助到大家理解清楚原理,那就点个赞呗~😄

在正式开始分析 Modifier 相关原理之前,建议你先看看 Compose 是如何将数据转换成 UI 的?这篇文章,当你了解了 Compose 的“组合”、“布局”、“绘制”的思维模型后,有助于你更透彻的了解 Modifier 的原理。


📓 什么是 Modifier ?


当你想深入了解 Modifier 的时候,你应该已经在实际代码中用起来了,随处可见各种 Modifier,比如:

  Modifier.size()         // 尺寸
  Modifier.width()        // 宽度
  Modifier.height()       // 高度
  Modifier.padding()      // 间距
  Modifier.background()   // 背景
  Modifier.clip()         // 裁切
  Modifier.clickable()    // 点击
  ... ...

那么这个像根部(起点)一样的 Modifier 是个啥?我们来看下它的定义:

// Modifier.kt

interface Modifier {

    // 重点:申明一个 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 接口的对象(即伴生对象),它的作用就是作为一个起点用于链式调用。

现在来看一段简单的代码:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Custom()
            }
        }
    }
}

@Composable
fun Custom() {
    Box(Modifier.size(40.dp).background(Color.Blue)) {}
}

这里我们自定义了一个 Composable 函数 custom(),内部就是一个 Box,同时对 Box 添加了尺寸和背景,效果如下:


在这里插入图片描述


现在我们定义的这个 Custom() 就是一个通用的可组合函数,如果现在我希望外部调用这个 Custom 函数的时候可以从外部去改变 Box 的透明度,该怎么写?

我们可能会很自然的写出下面的代码:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Custom(modifier = Modifier.alpha(0.5f))
            }
        }
    }
}

@Composable
fun Custom(modifier: Modifier) {
    Box(modifier.size(40.dp).background(Color.Blue)) {}
}

这段代码不难理解吧?效果如下:


在这里插入图片描述


我们可以给 Custom() 加一个 Modifier 类型的参数,这样外部就可以通过传入一个 Modifier 来修改 Box 的尺寸了,但是这就会存在一个问题:现在外部调用 custom() ,就必须传入一个 Modifier,这就不合理了,相当于强制要求外部必须加这个 Modifier 参数,所以针对这种情况,我们一般会像下面这么写:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Custom(modifier = Modifier.alpha(0.5f))  // 传了,custom 就用传入的 Modifier
                Custom()                                 // 不传,custom 就用默认的 Modifier
            }
        }
    }
}

@Composable
fun Custom(modifier: Modifier = Modifier) {              // 设定一个默认的 Modifier,作为一个占位符使用
    Box(
        modifier
            .size(40.dp)
            .background(Color.Blue)) {}
}

现在是不是就能解决我们前面的问题了?同时这也是一个很标准的写法

为什么说是一个标准写法?我们看下 Box 的源码定义:

@Composable
inline fun Box(
    modifier: Modifier = Modifier,  // 这里
    contentAlignment: Alignment = Alignment.TopStart,
    propagateMinConstraints: Boolean = false,
    content: @Composable BoxScope.() -> Unit
) 

再换一个 Button 看看:

@Composable
fun Button(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,  // 这里
    enabled: Boolean = true,
    ... ...
) 

再换一个 Column 看看:

@Composable
inline fun Column(
    modifier: Modifier = Modifier,  // 这里
    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
    content: @Composable ColumnScope.() -> Unit
) 

Compose 本身提供的函数参数也是这种标准写法,所以以后我们写自定义的 Compose 函数,按照这种标准写法就会很方便。


📓 Modifier 链


所谓的 Modifier 链 其实就是类似 Modifier.padding().background() 这样的链式调用。在实际开发过程中,这种链式调用对顺序的敏感度还是很强的,不同的调用顺序显示出来的结果会完全不一样。

接下来我们结合实际代码深入分析 Modifier 链的创建步骤。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Blue))
            }
        }
    }
}

前面你应该知道了 Modifier 是什么了,它就相当于一个白板,现在我们来看看 Modifier.background() 做了什么:

// Background.kt

fun Modifier.background(
    color: Color,
    shape: Shape = RectangleShape
) = this.then(     // this:就是 Modifier,这里又调用了 then()
    Background(
        color = color,
        shape = shape,
        inspectorInfo = debugInspectorInfo {
            name = "background"
            value = color
            properties["color"] = color
            properties["shape"] = shape
        }
    )
)

我们可以发现 Modifier.backgroud 实际上又调用了 this.then(Background()),此时 this 指针指向的是伴生对象 Modifier,接下来我们看看 then() 做了什么。

then()

// Modifier.kt

interface Modifier {
    infix fun then(other: Modifier): Modifier =
        if (other === Modifier) this else CombinedModifier(this, other)
}

我们可以看到 then() 是有参数的,它的参数也是一个 Modifier 类型的,所以传递进来的 Background() 也是一个 Modifier?

private class Background constructor(
    ... ...
) : DrawModifier, InspectorValueInfo(inspectorInfo)

很明显,Background() 实际上就是一个 DrawModifier。


现在我们再来看看 then() 的作用,它主要负责把调用的 Modifier(左边)和参数传入的 Modifier(右边)进行合并的

1. if (other === Modifier) this:如果参数是一个最基本的 Modifier 伴生对象:companion object,则返回自己,即调用者。

比如如果我这么写:

Box(Modifier.background(Color.Blue).then(Modifier))
==>
就会直接返回 Box(Modifier.background(Color.Blue) 自身

那我们例子里的 Modifier.background() 返回的是什么呢?

Box(Modifier.background(Color.Blue))
// 此时 then(Background()) 很明显不满足 other === Modifier 的条件,所以应该走 CombinedModifier?
// 请注意:Modifier 是一个伴生对象,它内部覆写了 then() 方法
// 如果你忘记了,可以回到上面看一下

companion object : Modifier {
    ... ...
    override infix fun then(other: Modifier): Modifier = other
}

Modifier 内部对 then() 的操作极其简单:你传进来什么,我就给你返回什么!

所以 Modifier.background() 这种调用方本身就是一个伴生对象的情况,会走到它自己内部的 then() 方法,返回的是 Background() 自身。


在这里插入图片描述


此时 Modifier 链的数据结构如下:


在这里插入图片描述


如果“调用者”和“传进来的参数”都不是 Modifier 伴生对象的话,就会走到下面一个条件。

2. else CombinedModifier(this, other):调用 CombinedModifier() 进行两个 Modifier 的融合,并返回 CombinedModifier 自身。

比如我们现在再添加一个尺寸:

Box(Modifier.background(Color.Blue).size(40.dp))

查看 size() 函数:

// Size.kt

fun Modifier.size(size: Dp) = this.then(
    SizeModifier(
        ... ...
    )
)

这个时候的 this 就是 Background() 了(也就是 DrawModifier),而 then() 内部的参数又是一个 SizeModifier,所以就要用到 CombinedModifier 进行融合了。

class CombinedModifier(
    internal val outer: Modifier,
    internal val inner: Modifier
) : Modifier

此时 Modifier 链的数据结构如下:


在这里插入图片描述


现在我们再添加一个边距:

Box(Modifier.background(Color.Blue).size(40.dp).padding(10.dp))

查看 padding() 函数:

// Padding.kt

fun Modifier.padding(all: Dp) =
    this.then(
        PaddingModifier(
            ... ...
        )
    )

这个时候的 this 指向的是 CombinedModifier 实例,而 then() 内部的参数又是一个 PaddingModifier,所以又要用到 CombinedModifier 进行融合了。

此时 Modifier 链的数据结构如下:


在这里插入图片描述


到这里,你有没有发现?Modifier 链条内部都有一个 then() 作为最外层调用,它就相当于一个套子,打造一个一环套一环的链条。


📓 CombinedModifier


接下来我们继续分析 CombinedModifier 又是个啥?

class CombinedModifier(
    internal val outer: Modifier,
    internal val inner: Modifier
) : Modifier

CombinedModifier 连接的两个 Modifier 分别存储在 outerinner 中,Compose 对 Modifier 的遍历,就是从外(outer)到内(inner)一层层访问。需要注意的是, outerinnner 字段都被 internal 关键字申明,意味着不能被外部模块直接访问,但官方为我们提供了 foldOut()foldIn() 专门用来遍历 Modifier 链。

class CombinedModifier(
    internal val outer: Modifier,
    internal val inner: Modifier
) : Modifier {
    override fun <R> foldIn(initial: R, operation: (R, Modifier.Element) -> R): R =
        inner.foldIn(outer.foldIn(initial, operation), operation)    // 正向遍历

    override fun <R> foldOut(initial: R, operation: (Modifier.Element, R) -> R): R =
        outer.foldOut(inner.foldOut(initial, operation), operation)  // 反向遍历

    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)    // 与运算
}

例如我们之前写的 Modifier 链:

Modifier.background(Color.Blue).size(40.dp).padding(10.dp)
  1. foldIn:正向遍历 Modifier 链,DrawModifier -> SizeModifier -> PaddingModifier
  2. foldOut:反向遍历 Modifier 链,PaddingModifier -> SizeModifier -> DrawModifier

📓 foldIn() 解析


你应该也发现了,CombinedModifier 是 Modifier 接口的实现类,它内部的 foldInfoldOut 都是覆写了其父接口 Modifier 的内部方法,所以我们可以先来看看 Modifier 的 foldIn(因为它更简单):

interface Modifier {
    fun <R> foldIn(initial: R, operation: (R, Element) -> R): R
}

让我们分析一下这个 foldIn 函数:

  1. fun <R>:这是一个泛型函数,类型参数 R 表示结果的类型。
  2. initial: R:作为累积操作的初始值,foldIn 从这个值开始,并在每次操作中更新它。
  3. operation: (R, Element) -> R:这是一个 lambda 表达式参数,它接受当前的累积值和修饰符链中的当前元素 Element,然后返回新的累积值。这个 lambda 表达式会被应用于修饰符链中的每一个元素。

举个例子,比如说我们希望统计 Modifier 链中有 Modifier 的数量:

val modifier = Modifier
    .background(Color.Blue)
    .size(40.dp)
    .padding(10.dp)
val result = modifier.foldIn<Int>(0) { currentIndex, element ->
    println("@@@ index: $currentIndex , element :$element")
    currentIndex + 1
}
println("@@@ result = $result")

看不懂?那我们直接查看 Log:

@@@ index: 0 , element :Background(color=Color(0.0, 0.0, 1.0, 1.0, sRGB IEC61966-2.1), brush=null, alpha = 1.0, shape=RectangleShape)
@@@ index: 1 , element :androidx.compose.foundation.layout.SizeModifier@78000000
@@@ index: 2 , element :androidx.compose.foundation.layout.PaddingModifier@b80004cf
@@@ result = 3

看完最简单的 foldIn 函数,我们再来看 CombinedModifier 的 foldIn 函数:

// Modifier 接口的 foldIn 函数
fun <R> foldIn(initial: R, operation: (R, Element) -> R): R

// Combinedmodifier 覆写的 foldIn 函数
override fun <R> foldIn(initial: R, operation: (R, Modifier.Element) -> R): R =
    inner.foldIn(outer.foldIn(initial, operation), operation)

看不懂是么?不急,你先来想想 Modifier 链条经过 CombinedModifier 转换后,是不是下面这个样子:

Modifier.background(Color.Blue).size(40.dp).padding(10.dp)

// 分步骤拆解
1. Modifier.background(Color.Blue)
   // 等同于
   Modifier.then(Modifier.background())  -->  Modifier.background()

2. Modifier.background(Color.Blue).size(40.dp)
   // 等同于
   Modifier.background().then(Modifier.size())
   // 等同于
   CombinedModifier(
       Modifier.background(),
       Modifier.size()
   )
3. Modifier.background(Color.Blue).size(40.dp).padding(10.dp)
   // 等同于
   Modifier.background().then(Modifier.size()).then(Modifier.padding())
   // 等同于
   CombinedModifier(
      CombinedModifier(
          Modifier.background(),
          Modifier.size()
      ),
      Modifier.padding()
   )

再看个示例图,可能会更直观点:


在这里插入图片描述


现在我们来分析下 CombinedModifier 的 foldIn 函数:

override fun <R> foldIn(initial: R, operation: (R, Modifier.Element) -> R): R =
    inner.foldIn(outer.foldIn(initial, operation), operation)

在这个 foldIn 实现中,可以看到处理修饰符链的过程是递归的和分层的。让我们逐步解析这个函数:

  1. initial: R:是累积操作的初始值。
  2. operation: (R, Modifier.Element) -> R:是一个应用于每一个 Modifier.Element 的高阶函数,它接受两个参数:当前的累积值和 Modifier 的一个元素,并返回新的累积值。
  3. inner.foldIn(outer.foldIn(initial, operation), operation):将上一步的结果作为它的初始值,并在 inner 修改器链上执行相同的 operation 函数。

感觉好复杂啊,没关系不慌,看图:


在这里插入图片描述


到这里我们再来回顾下刚才统计 Modifier 数量的例子:

val modifier = Modifier
    .background(Color.Blue)
    .size(40.dp)
    .padding(10.dp)
val result = modifier.foldIn<Int>(0) { currentIndex, element ->
    println("@@@ index: $currentIndex , element :$element")
    currentIndex + 1
}
println("@@@ result = $result")

Log 如下:

@@@ index: 0 , element :Background(color=Color(0.0, 0.0, 1.0, 1.0, sRGB IEC61966-2.1), brush=null, alpha = 1.0, shape=RectangleShape)
@@@ index: 1 , element :androidx.compose.foundation.layout.SizeModifier@78000000
@@@ index: 2 , element :androidx.compose.foundation.layout.PaddingModifier@b80004cf
@@@ result = 3

现在是不是就能毫无压力的看明白了?再来一张图:


在这里插入图片描述



📓 foldOut() 解析


foldIn() 相反, 我们就不再单独展开分析它的参数了,还是以前面统计 Modifier 个数的例子看下 Log 输出:

val modifier = Modifier
    .background(Color.Blue)
    .size(40.dp)
    .padding(10.dp)

val result = modifier.foldOut<Int>(0) { element, currentIndex ->
    println("@@@ index: $currentIndex , element :$element")
    currentIndex + 1
}

println("@@@ result = $result")

看下 Log:

@@@ index: 0 , element :androidx.compose.foundation.layout.PaddingModifier@b80004cf
@@@ index: 1 , element :androidx.compose.foundation.layout.SizeModifier@78000000
@@@ index: 2 , element :Background(color=Color(0.0, 0.0, 1.0, 1.0, sRGB IEC61966-2.1), brush=null, alpha = 1.0, shape=RectangleShape)
@@@ result = 3

是不是反过来的?另外注意 lambda 表达式内部的参数位置与 foldIn() 是反着来的,其它没有任何区别。


📓 Modifier.Element


在 Modifier 整个体系里面,有很多 Modifier 接口的子接口和实现类,除了我们之前提过的 companion object 伴生对象和 CombinedModifier 类之外,其他所有的 Modifier,不管是接口还是实现类,全部都直接或者间接的继承了 Modifier 的另外一个子接口:Element


在这里插入图片描述


比如我们前面遇到的 Background 背后的 DrawModifier:

interface DrawModifier : Modifier.Element {
    fun ContentDrawScope.draw()
}

又比如 SizeModifier:

private class SizeModifier(
    ... ...
) : LayoutModifier, InspectorValueInfo(inspectorInfo)

interface LayoutModifier : Modifier.Element {}

又比如 PaddingModifier:

private class PaddingModifier(... ...
) : LayoutModifier, InspectorValueInfo(inspectorInfo)

interface LayoutModifier : Modifier.Element {}

你会发现它们的根统统都是 Modifier.Element 这个接口。

像这类直接子接口或子类还有哪些呢,如图所示,这些接口和子类基本涵盖了 Modifier 所提供的所有能力。

在这里插入图片描述

文章来源:https://blog.csdn.net/pepsimaxin/article/details/135691837
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。