在 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 = CombinedModifier(Modifier ,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 = CombinedModifier(Modifier ,SizeElement)
val chain2 = CombinedModifier(chain1 ,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 = CombinedModifier(Modifier ,SizeElement)
val chain2 = CombinedModifier(chain1 ,BackgroundElement) // 等同于 CombinedModifier(CombinedModifier(Modifier ,SizeElement) ,BackgroundElement)
val chain3 = CombinedModifier(chain2 ,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 创建时机是在组合过程中进行创建的