在 Compose 中,Modifier 的调用顺序不同,它造成的结果是不一样的,而不像原生的 XML 一样,顺序并不会影响界面绘制的结果。而在 Compose 中,它是按照Modifier 顺序执行的,同时 Modifier 它会形成一个链表的数据结构,我们编写如下的代码,看看它的这个链是怎么样的,是如何按照顺序执行的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Preview
@Composable
fun modifier() {
    Box(
        modifier = Modifier
            .size(200.dp)
            .background(Color.Blue)
            .padding(10.dp)
            .clickable { }
    ) {
    }
}

代码很简单,就是针对这个 Box 设置了大小,背景,边距,和一个点击事件而已,然后点进 Modifier 中,看看里面的源代码是怎么样的

Modifier

在默认情况下,如果传递的参数仅仅是 Modifier,或者是不传递,因为不传递内部默认就是赋值了 Modifier,也就是 Box (modifier = Modifier) 调用的这个伴生对象,Modifier 是一个接口,它有如下默认方法

1
2
3
4
5
6
7
8
@Composable
inline fun Box(
    modifier: Modifier = Modifier, // 默认值就是 Modifier
) {
  ....
}

Box (modifier = Modifier) 等价于 Box ()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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"
  	  }
}

Box(modifier = Modifier)  // 这样调用的就是 Modifier 的伴生对象 

我们按照上面的 Modifier 的调用链来分析一下,假设现在调用的 size 方法,看下 size 方法里面实现的是什么

1
2
3
4
5
6
7
8
@Stable
fun Modifier.height(height: Dp) = this.then(
    SizeElement(
        minHeight = height,
        maxHeight = height,
        enforceIncoming = true
    )
)

then 、CombinedModifier

可以看到使用 this 调用了一个 then 方法,这个 this 也就是刚才说的 Modifier 伴生对象

1
infix fun then(other: Modifier): Modifier = if (other === Modifier) this else CombinedModifier(this, other)

可以看到 then 是一个中缀函数,这个方法如果传入的是同一个对象,那么返回的还是本身,否则的情况下调用的是 CombinedModifier

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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)

    override fun equals(other: Any?): Boolean =
        other is CombinedModifier && outer == other.outer && inner == other.inner

    override fun hashCode(): Int = outer.hashCode() + 31 * inner.hashCode()

    override fun toString() = "[" + foldIn("") { acc, element ->
        if (acc.isEmpty()) element.toString() else "$acc, $element"
    } + "]"
}

在 CombinedModifier 中,可以发现有 inner 和 outer 属性,在上述示例代码中,inner 代表的是 Modifier 伴生对象 outer 代表的是 SizeElement,所以,它现在调用链就变成这样了

1
val chain1 = CombinedModifierModifier SizeElement

然后再调用 background

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
fun Modifier.background(
    color: Color,
    shape: Shape = RectangleShape
): Modifier {
    val alpha = 1.0f // for solid colors
    return this.then(
        BackgroundElement(
            color = color,
            shape = shape,
            alpha = alpha,
            inspectorInfo = debugInspectorInfo {
                name = "background"
                value = color
                properties["color"] = color
                properties["shape"] = shape
            }
}

同样的,内部也是调用的 this.then()但是不同的是此时,this 代表的是我们上一步创建好了的 CombinedModifier 对象了,而通过 this.then() 又再次创建了一个 CombinedModifier 并且链上了 BackgroundElement ,所以,这个步骤完就变成这个样子了

1
2
3
val chain1 = CombinedModifierModifier SizeElement

val chain2 = CombinedModifierchain1 BackgroundElement // 等同于  CombinedModifier(CombinedModifier(Modifier ,SizeElement) ,BackgroundElement)

这里的 BackgroundElement 我们再点进去看一下,可以看下它的结构是这样的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private class BackgroundElement(
    private val color: Color = Color.Unspecified,
    private val brush: Brush? = null,
    private val alpha: Float,
    private val shape: Shape,
    private val inspectorInfo: InspectorInfo.() -> Unit
) : ModifierNodeElement<BackgroundNode>() {

}

// ModifierNodeElement
abstract class ModifierNodeElement<N : Modifier.Node> : Modifier.Element, InspectableValue {
}

// Modifier.Element
@JvmDefaultWithCompatibility
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)
}

可以看到 BackgroundElement 继承了 ModifierNodeElement, 然后 ModifierNodeElement 是一个抽象类,然后它实现了一个 Modifier.Element 接口,其中这个接口的内部实现重写了 foldIn 和 foldOut 方法,其实很多 Modifier 调用链的节点其实都实现了 Modifier.Element ,包括上一个步骤的 SizeElement ,所以我们也可以认为它就是一个 Modifier.Element,这里需要留意的 foldIn 和 foldOut,它和 Modifier 的伴生对象方法实现又有一些不同了

紧接着再调用 padding 的设置

1
2
3
4
5
6
7
fun Modifier.padding(all: Dp) = this then PaddingElement(
    start = all,
    top = all,
    end = all,
    bottom = all,
    rtlAware = true
)

这里可以发现不同的是调用的是 this then PaddingElement,而不是 this.then() 了,其实是一样的,因为 kotlin 中缀函数,可以省略不写括号,那么到了这一步,它就是变成这样了

1
2
3
4
5
val chain1 = CombinedModifierModifier SizeElement

val chain2 = CombinedModifierchain1 BackgroundElement // 等同于  CombinedModifier(CombinedModifier(Modifier ,SizeElement) ,BackgroundElement)

val chain3 = CombinedModifierchain2 SizeElement// 等同于 CombinedModifier(CombinedModifier(CombinedModifier(Modifier ,SizeElement) ,BackgroundElement) ,SizeElement)

到了这一步,可以发现他通过 CombinedModifier 的 inner 和 outer 属性,不断的将 Modifier 过程中产生的对象链在一起了,所以到了这一步 chain3 的链就是这样的

1
SizeElement  BackgroundElement PaddingElement

到了这里,就有一个疑问了,我们这里已经了解了它是如何将整个过程中的 Modifier 节点如何链在一起了,那么后续它是怎么使用的呢,或者说是怎么取出来的呢,其实在 CombinedModifier 方法中,我们之前还有两个方法没有讲,那就是 foldIn 和 foldOut,两个方法都是遍历

  • foldIn 正向遍历 Modifier 链
  • foldOut 反向遍历 Modifier 链

    1
    2
    3
    4
    5
    
    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)

以 foldIn 举例

1
2
3
4
5
6
7
8
9
    val modifier = Modifier
        .size(200.dp)
        .background(Color.Blue)
        .padding(10.dp)

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

这里我先假设 Modifier 的调用链只有 size 和 background,来举例说明,会更加简单一些,首先再经过 Modifier 的调用链之后,他们会被 CombinedModifier 给组合起来,此时这个时候 CombinedModifier 对应的 outer 是 SizeElement ,inner 是 BackgroundElement,CombinedModifier 当调用 foldIn 的时候,会先调用内部的 outer.foldIn 方法, 而这个时候 SizeElement 同时也是一个 Modifier.Element ,它会调用它的内部的方法(这是我们上面介绍过的)

1
2
3
// Modifier.Element  的  foldIn 方法
override fun <R> foldIn(initial: R, operation: (R, Element) -> R): R =
        operation(initial, this) // 返回本身了

而 Modifier.Element 方法调用 foldIn 方法,它将我们传入的 operation 直接返回了,对应上述的 currentIndex 和 element , currentIndex 就是我们传入的初始值,element 是自身,也就是 SizeElement

处理完了 outer.foldIn(initial, operation) 方法,然后再是处理 inner.foldIn() 方法,思路和 outter 调用是一样的,它返回的就是 BackgroundElement,所以当我们这里只有这两个节点的时候,这个遍历就已经完成了。而如果针对是多个链,无非就是更多的节点被 CombinedModifier 给包裹起来了,那么就是通过 CombinedModifier foldIn方法不断重复的递归调用,最终完成整个链的遍历

简单一点理解就是,比如有 size background padding 这样的几个节点,foldIn 就是从左往右遍历,而 foldout 就是反过来了,从右往左。然后上面的代码运行后,日志如下

这个时候还有最后一个 clickable 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fun Modifier.clickable(
    enabled: Boolean = true,
    onClickLabel: String? = null,
    role: Role? = null,
    onClick: () -> Unit
) = composed() {
    Modifier.clickable(
        enabled = enabled,
        onClickLabel = onClickLabel,
        onClick = onClick,
        role = role,
        indication = LocalIndication.current,
        interactionSource = remember { MutableInteractionSource() }
    )
}

可以发现,这个方法和之前的都不一样了,它调用的是 composed 方法

1
2
3
4
fun Modifier.composed(
    inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo,
    factory: @Composable Modifier.() -> Modifier
): Modifier = this.then(ComposedModifier(inspectorInfo, factory))

而随后又调用了 ComposedModifier

1
2
3
4
private open class ComposedModifier(
    inspectorInfo: InspectorInfo.() -> Unit,
    val factory: @Composable Modifier.() -> Modifier
) : Modifier.Element, InspectorValueInfo(inspectorInfo)

可以发现 ComposedModifier 它是一个私有的不允许直接创建,它的作用是通过 factory 工厂函数在组合过程时执行 factory 创建出 Modifier 并使用,所以这个时候的 Modifier.clickable 在这个过程中,并没有直接的创建,而是将这个 Modifier 存到 ComposedModifier 中的工厂函数中了,然后它会在组合的过程中,将它创建好

那么它会在哪里执行这个工厂函数呢 ?可以看下 Box 函数中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Composable
inline fun Box(
    modifier: Modifier = Modifier,
    contentAlignment: Alignment = Alignment.TopStart,
    propagateMinConstraints: Boolean = false,
    content: @Composable BoxScope.() -> Unit
) {
    val measurePolicy = rememberBoxMeasurePolicy(contentAlignment, propagateMinConstraints)
    Layout(
        content = { BoxScopeInstance.content() },
        measurePolicy = measurePolicy,
        modifier = modifier
    )
}

然后再看下 Layout 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Composable
inline fun Layout(
    content: @Composable @UiComposable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {
    val compositeKeyHash = currentCompositeKeyHash
    val localMap = currentComposer.currentCompositionLocalMap
    ReusableComposeNode<ComposeUiNode, Applier<Any>>(
        factory = ComposeUiNode.Constructor,
        update = {
            set(measurePolicy, SetMeasurePolicy)
            set(localMap, SetResolvedCompositionLocals)
            @OptIn(ExperimentalComposeUiApi::class)
            set(compositeKeyHash, SetCompositeKeyHash)
        },
        skippableUpdate = materializerOf(modifier), // 最终会调用这个方法
        content = content
    )
}

其他的方法不用管,看下 materializerOf 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
internal fun materializerOf(
    modifier: Modifier
): @Composable SkippableUpdater<ComposeUiNode>.() -> Unit = {
    val compositeKeyHash = currentCompositeKeyHash
    val materialized = currentComposer.materialize(modifier)
    update {
        set(materialized, SetModifier)
        @OptIn(ExperimentalComposeUiApi::class)
        set(compositeKeyHash, SetCompositeKeyHash)
    }
}

最后它的加载会在 materialize 方法中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fun Composer.materialize(modifier: Modifier): Modifier {
    if (modifier.all { it !is ComposedModifier }) {
        return modifier
    }

    startReplaceableGroup(0x48ae8da7)

    val result = modifier.foldIn<Modifier>(Modifier) { acc, element ->
        acc.then(
            if (element is ComposedModifier) { // 判断是不是 ComposedModifier 
                @Suppress("UNCHECKED_CAST")
                val factory = element.factory as Modifier.(Composer, Int) -> Modifier // 创建 Modifier
                val composedMod = factory(Modifier, this, 0)
                materialize(composedMod)
            } else {
                element
            }
        )
    }

    endReplaceableGroup()
    return result
}

最终,它会通过 foldIn 遍历的时候,判断是否是 ComposedModifier 类型,然后创建这个 Modifier ,需要注意的是,它是一个递归函数,然后创建好的 Modifier 同样是通过 then 函数链在之前的那条链上了,所以这个 composed 函数,可以认为它是一个懒加载函数,它是在组件在进行组合的过程中才去创建的

那么问题来了,为什么要这么做呢,我直接像 Modifier 那样直接提前创建不可以吗 ?

这个问题,其实在 composed 函数中有解释

它大概的意思是,它是一个带有状态的 Modifier 且是可重用,状态独立的

那这又是什么意思呢?我们举一个例子来看看就知道了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Composable
fun testComposedModifier() {
    Column {
        var paddingValue by remember { mutableStateOf(10.dp) }

        val modifier = Modifier
            .padding(paddingValue)
            .clickable { paddingValue = 20.dp }

        Box(modifier = Modifier.background(Color.Blue) then modifier)

        Text(text = "testComposedModifier", modifier = Modifier.background(Color.Red) then modifier)
    }
}

这段代码我们的 Box 和 Text 都使用了同一个 modifier ,并且这个 modifier 实现了一个点击事件,在这个点击事件中,将 paddingValue 的值变大了,我们运行一下代码然后点击一下 Text 组件,看看会发生什么效果

可以发现我们点击的是 Text 组件,但是 Box 组件的间隙也变大了,这是因为共用的是同一个 paddingValue 状态

我们再试试使用 composed 的情况来看看

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Preview
@Composable
fun testComposedModifier2() {
    Column {
        val modifier = Modifier.composed {

            var paddingValue by remember { mutableStateOf(10.dp) }

            padding(paddingValue)
            .clickable { paddingValue = 20.dp }
        }

        Box(modifier = Modifier.background(Color.Blue) then modifier)

        Text(text = "testComposedModifier", modifier = Modifier.background(Color.Red) then modifier)
    }
}

代码改动的地方不多,只是将之前 Modifier 的放在了 composed 内部了,同时将 paddingValue 也放在函数内部了,然后再运行代码看一下

这个时候我们点击的仍然还是 Text 组件,但是发生的效果不一样了,只有 Text 组件在发生改变了,这就是我们之前说到的,使用 composed 创建的 Modifier 函数,它拥有独立的内部状态,因为它在每个组件在组合过程中,都是创建的组件独有的一个新的对象,同时我们还可以发现它的内部可以使用 @Composable 函数,是因为它的工厂函数,有提供了 @Composable 上下文环境,所以 composed(ComposedModifier) 函数总结如下

  • composed 的内部有 Composable 的上下文环境,内部可以使用带有 @Composable 注解的函数
  • composed 是状态独立的,且是可复用的
  • composed 内部的 Modifier 创建时机是在组合过程中进行创建的