文章目录
在前面的章节或多或少的都有简单介绍了下什么是 Modifier 以及 Modifier 的一些简单配置,例如 Modifier.size()、Modifier.width()、Modifier.height()、Modifier.padding()、Modifier.background()、Modifier.clip() 等等。
虽然知道有很多配置,但 Modifier 还有哪些配置?怎么能更好的使用这些 Modifier?带着疑问接下来将会对 Modifier 做一个专题深入讲解。
modifier:Modifier = Modifier 的含义
如果我们要写一个最简单的 Modifer 会是怎样的?
Modifier
是的,最简单的 Modifier 就是直接写 Modifier。Modifier 本身是一个接口,但当我们用 IDE 点击定位到 Modifier 时,会直接跳到一个实现了 Modifier 的伴生对象:
@Suppress("ModifierFactoryExtensionFunction")
@Stable
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 对象(后续 Modifier 伴生对象都用 Modifier.Companion 表示)。我们用一个简单的案例说明。
假设我们想对自定义 Composable 从外部修改相关的配置,就可以将 Modifier 作为自定义 Composable 的参数传进来:
setContent {
Custom(Modifier.size(40.dp))
}
@Composable
fun Custom(modifier: Modifier) {
// 注意这里使用了传进来的参数,modifier 是小写
Box(modifier.background(Color.Blue) {}
}
但如果在调用的地方我不想设置大小了,不想传参,kotlin 是可以给函数参数提供默认值的,那么默认值应该填什么呢?就是 Modifier:
setContent {
Custom()
}
@Composable
fun Custom(modifier: Modifier = Modifier) { // 默认值 Modifier
Box(modifier.background(Color.Blue) {}
}
所以 Modifier.Companion 一般用法就是用来作为 Modifier 函数参数的默认值使用。
Modifier.Companion 另外的一个作用是想创建一个 Modifier 时会无意间用到它。该怎么理解呢?继续看代码:
// padding() 和 backgroud() 都是扩展函数
// 不断拼接配置效果
Modifier.padding(40.dp).background(Color.Blue)
Padding.kt
@Stable
fun Modifier.padding(
horizontal: Dp = 0.dp,
vertical: Dp = 0.dp
) = this.then( // 获取 this 拿到上一个 Modifier.Companion 对象
PaddingModifier(
start = horizontal,
top = vertical,
end = horizontal,
bottom = vertical,
rtlAware = true,
inspectorInfo = debugInspectorInfo {
name = "padding"
properties["horizontal"] = horizontal
properties["vertical"] = vertical
}
)
)
Background.kt
fun Modifier.background(
color: Color,
shape: Shape = RectangleShape
) = this.then( // 获取 this 拿到上一个 Modifier.Companion 对象
Background(
color = color,
shape = shape,
inspectorInfo = debugInspectorInfo {
name = "background"
value = color
properties["color"] = color
properties["shape"] = shape
}
)
)
Modifier.padding() 和 Modifier.background() 是 Modifier 的扩展函数,实际上在调用扩展函数之前就会无意间创建了 Modifier.Companion。
还有一个小知识点是,Compose 官方建议,如果有 Modifier 作为函数参数时,建议将 Modifier 作为第一个函数参数,这是因为当有多个函数参数有多个默认值时,第一个函数参数不需要指明是什么参数名:
setContent {
// Custom(modifier = Modifier.size(40.dp))
Custom(Modifier.size(40.dp)) // 第一个参数默认值不需要指明参数名
}
@Composable
fun Custom(modifier: Modifier = Modifier) {
Box(modifier.background(Color.Blue)) {}
}
小结一下 Modifier.Companion 的作用:
-
作为自定义 Composable 传参 Modifier 的默认值
-
想创建一个 Modifier 时会无意间用到
then()、CombinedModifier 和 Modifier.Element
then()
我们都知道 Modifier 是对调用顺序敏感的,而且 Modifier 可以很方便的进行链式调用:
Modifier.padding(20.dp).background(Color.Green)
在程序编写 Modifier 时是创建了 Modifier.Companion,Modifier.padding() 则是在 Modifier.Companion 基础上添加 padding;background() 则是对 Modifier.padding() 返回的 Modifier 再添加 background。最终的配置效果是按顺序拼接起来的。
Modifier.padding()、Modifier.background() 这些扩展函数是怎么做到对配置拼接的?我们选取 Modifier.background() 的源码了解原理:
Background.kt
fun Modifier.background(
color: Color,
shape: Shape = RectangleShape
) = this.then( // this 就是上一个 Modifier
Background(
color = color,
shape = shape,
inspectorInfo = debugInspectorInfo {
name = "background"
value = color
properties["color"] = color
properties["shape"] = shape
}
)
)
private class Background constructor(
private val color: Color? = null,
private val brush: Brush? = null,
private val alpha: Float = 1.0f,
private val shape: Shape,
inspectorInfo: InspectorInfo.() -> Unit
) : DrawModifier, InspectorValueInfo(inspectorInfo) {
...
}
DrawModifier.kt
interface DrawModifier : Modifier.Element {
fun ContentDrawScope.draw()
}
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)
}
可以看到在 Modifier.background() 扩展函数调用了 then() 函数,在 then() 函数传了一个对象 Background,这个对象也是一个实现了 Modifier 的对象。
Modifier.kt
infix fun then(other: Modifier): Modifier =
if (other === Modifier) this else CombinedModifier(this, other)
then() 是在 Modifier 接口里面的一个函数,它的作用是将调用这个 then() 函数的 Modifier 对象和 then() 传参进来的 Modifier 进行合并。
需要注意的是,then() 函数是可以被子类重写的,比如 Modifier.Companion 就重写了:
Modifier.kt
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
// 重写了 then() 函数,直接返回的传参的对象
override infix fun then(other: Modifier): Modifier = other
override fun toString() = "Modifier"
}
Modifier.Companion 的 then() 直接返回了传参对象,也就是 Modifier.padding()、Modifier.background() 其实返回的是自己的传参,例如 Modifier.background() 就是返回的实现了 Modifier 的 Background 对象:
Modifier.background()
等价于
Modifier.Companion.then(Background())
等价于
Background()
除了 Modifier.Companion 的 then() 函数是返回传参,那其他的 Modifier 是怎么合并起来的呢?在 Compose 使用的是 CombinedModifier:
Modifier.padding().then(Modifier.background())
等价于
CombinedModifier(Modifier.padding(), Modifier.background())
Modifier.padding()
.then(Modifier.background())
.then(Modifier.size())
等价于
CombinedModifier(
CombinedModifier(Modifier.padding(), Modifier.background()),
Modifier.size()
)
Modifier 就是这样通过调用 then() 函数创建 CombinedModifier 将多个 Modifier 合并,一层套一层的将配置效果拼接起来。
小结一下 then() 函数:
- 当使用 Modifier.Companion 调用 then() 函数,传参的 Modifier 如果是 Modifier.Companion,直接返回的 Modifier.Companion 自身
Modifier.then(Modifier)
等价于
Modifier
- 当使用 Modifier.Companion 调用 then() 函数,传参的 Modifier 不是 Modifier.Companion,会直接返回 then() 函数传参的 Modifier 对象
Modifier.then(Modifier.padding())
等价于
Modifier.padding()
- 当使用其他实现了 Modifier 接口的实现类调用 then() 函数,会使用 CombinedModifier 将当前 Modifier 和 then() 传参的 Modifier 合并
Modifier.padding().then(Modifier.background())
等价于
CombinedModifier(Modifier.padding(), Modifier.background())
CombinedModifier 和 Modifier.Element
除了 Modifier.Companion 和 CombinedModifier 之外,基本上其他的 Modifier 实现类都直接或间接的实现了另一个 Modifier 子接口:Modifier.Element。
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)
}
class CombinedModifier(
private val outer: Modifier,
private val inner: Modifier
) : Modifier {
// 先加入的 Modifier 先应用
override fun <R> foldIn(initial: R, operation: (R, Modifier.Element) -> R): R =
inner.foldIn(outer.foldIn(initial, operation), operation)
// 后加入的 Modifier 先应用
override fun <R> foldOut(initial: R, operation: (Modifier.Element, R) -> R): R =
outer.foldOut(inner.foldOut(initial, operation), operation)
// 至少有一个 Modifier 符合条件就返回 true
override fun any(predicate: (Modifier.Element) -> Boolean): Boolean =
outer.any(predicate) || inner.any(predicate)
// 所有 Modifier 符合条件就返回 true
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"
} + "]"
}
foldIn()、foldOut()、any() 和 all() 这几个函数并不是用来做通用功能的,而是主要为了 CombinedModifier 这个类而创造的。
-
any():在 Modifier.Element 它是传参一个条件判断,直接将自身返回,是为了查看 CombinedModifier 是否至少有一个符合条件的,也就是 CombinedModifier 的 inner 和 outer 两个参数,包括 inner 或 outer 内的 CombinedModifier
-
all():查看 CombinedModifier 是否所有都符合条件
其他 Modifier 内部没有包含其他 Modifier,而为了能配合 CombinedModifier 的遍历,所以其他 Modifier 都实现了 Modifier.Element 提供的默认处理。
foldIn() 和 foldOut() 也是为了配合 CombinedModifier 的遍历。
-
foldIn():先加入的 Modifier 先应用
-
foldOut():展开来后加入的 Modifier 先应用
Modifier.Companion 也有实现这几个函数,但它的返回都在提示你:请忽略我。Modifier.Companion 相当于一张白纸,作为最简单最基本的 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"
}
接下来对 CombinedModifier 和 Modifier.Element 做一个小结:
-
CombinedModifier 是为了创建 Modifier 链条的、没有实际效果的 Modifier
-
Modifier.Element 是除了 Modifier.Companion 和 CombinedModifier 外,其他 Modifier 直接或间接的父接口,它只是为了配合 CombinedModifier 使用,而它自身并没有什么作用
Modifier.composed() 和 ComposedModifier
ComposedModifier.kt
private open class ComposedModifier(
inspectorInfo: InspectorInfo.() -> Unit,
val factory: @Composable Modifier.() -> Modifier
) : Modifier.Element, InspectorValueInfo(inspectorInfo)
ComposedModifier 是私有的它并不允许直接创建,看源码也能发现它实现了 Modifier.Element,本身并没有任何功能实现。
ComposedModifier 的作用是通过 factory 工厂函数在组合过程时执行 factory 创建出 Modifier 并使用。
创建 ComposedModifier 需要使用 Modifier.composed() 扩展函数:
ComposedModifier.kt
fun Modifier.composed(
inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo,
factory: @Composable Modifier.() -> Modifier
): Modifier = this.then(ComposedModifier(inspectorInfo, factory))
// 使用 Modifier.composed()
setContent {
Box(Modifier.composed { Modifier.padding(8.dp) })
}
正常情况下当程序执行到例如 Modifier.padding() 时就会直接创建 Modifier,而 Modifier.composed() 需要在 lambda 返回一个 Modifier,程序执行到 lambda 时并不会立即创建 Modifier,而是将提供的 Modifier 存到 ComposedModifier 的工厂函数里,等到组合过程再创建。
那到底会在什么时候执行工厂函数?我们深入到 Box() 源码跟踪 Modifier 被调用的地方:
Box.kt
@Composable
fun Box(modifier: Modifier) {
Layout({}, measurePolicy = EmptyBoxMeasurePolicy, modifier = modifier)
}
Layout.kt
@Suppress("ComposableLambdaParameterPosition")
@Composable inline fun Layout(
content: @Composable () -> Unit,
modifier: Modifier = Modifier,
measurePolicy: MeasurePolicy
) {
...
ReusableComposeNode<ComposeUiNode, Applier<Any>>(
...
skippableUpdate = materializerOf(modifier),
...
)
}
@PublishedApi
internal fun materializerOf(
modifier: Modifier
): @Composable SkippableUpdater<ComposeUiNode>.() -> Unit = {
val materialized = currentComposer.materialize(modifier)
...
}
ComposedModifier.kt
@Suppress("ModifierFactoryExtensionFunction")
fun Composer.materialize(modifier: Modifier): Modifier {
...
// foldIn() 将所有 Modifier 按顺序遍历
// 执行所有 ComposedModifier 的工厂函数并返回 Modifier,否则返回自身
// 最终重新用 then() 再次将所有 Modifier 拼接
val result = modifier.foldIn<Modifier>(Modifier) { acc, element ->
acc.then(
// 如果是 ComposedModifier,就执行 factory 工厂函数获取 Modifier
if (element is ComposedModifier) {
@kotlin.Suppress("UNCHECKED_CAST")
val factory = element.factory as Modifier.(Composer, Int) -> Modifier
val composedMod = factory(Modifier, this, 0)
// 递归将所有 ComposedModifier 的工厂函数都执行
materialize(composedMod)
} else element
)
}
endReplaceableGroup()
return result
}
从源码可以查看到 Modifier.composed() 确实会在组合过程中对所有 Modifier 遍历,查找 ComposedModifier 并执行工厂函数返回的 Modifier,最后再次将所有 Modifier 使用 then() 拼接起来。
到这里就会有疑问了:ComposedModifier 这么做有什么作用?这和直接写 Modifier 有啥区别?
这个答案可以在 Modifier.composed 的注释里找到:
注释中提到了几个关键词:stateful modifiers 有状态的 Modifier,reused 重用,element-specific state 状态独有。
该怎么理解这几个词的意思呢?还是看代码:
setContent {
val modifier = Modifier.composed { Modifier.padding(8.dp) }
// val modifier = Modifier.padding(8.dp)
Box(modifier)
Text("vincent", modifier)
}
Modifier.padding() 和 Modifier.composed() 唯一的区别是,Modifier.composed() 修改了内部 lambda 创建 Modifier 的时间节点,但是对显示结果而言它们二者是没有任何区别的。
实际上 Modifier.composed() 不是用在案例写的那种场景下,而是用在有状态的 Modifier 的场景。
什么叫有状态的 Modifier?按上面的例子 8.dp 就是一个状态,不过因为在这里已经写死了数值所以是无状态的,所以也不是 Modifier.composed() 的使用场景。
setContent {
val modifier = Modifier.composed {
var padding by rememeber { mutableStateOf(8.dp) }
Modifier.padding(padding).clickable { padding = 0.dp }
}
Box(modifier)
Text("vincent", modifier)
}
代码中我们将 8.dp 另外提取了出来为一个变量 padding,这时候 padding 就是一个状态,此时 modifier 它就是有状态的,而且每个使用 modifier 的 Composable 的状态都是独立的。
我们可以用一个演示代码验证 Modifier 是否为独立状态:
setContent {
var padding by remember { mutableStateOf(8.dp) }
val modifier = Modifier.padding(padding).clickable { padding = 0.dp }
Column {
// Box 和 Text 共用同一个 padding 状态
// 点击任意一个 Composable,二者的内边距都会被修改为 0dp
Box(Modifier.background(Color.Blue) then modifier)
Text("vincent", Modifier.background(Color.Green) then modifier)
}
}
setContent {
val modifier = Modifier.composed {
var padding by rememeber { mutableStateOf(8.dp) }
Modifier.padding(padding).clickable { padding = 0.dp }
}
Column {
// Box 和 Text 使用 Modifier.composed()
// 因为状态独立,点击不同的 Composable 只会影响对应 Composable 的内边距
Box(Modifier.background(Color.Blue) then modifier)
Text("vincent", Modifier.background(Color.Green) then modifier)
}
}
可以发现使用 Modifier.composed() 返回的 Modifier 分别设置给不同的 Composable,点击 Composable 后蓝色方块并没有消失,这说明 Box() 和 Text() 使用的不同的 Modifier 状态,所以不会互相影响。
不过这个验证还是不能说明 Modifier.composed() 的作用和具体使用场景,因为我们想要实现 Modifier.composed() 分别有独立的状态,我们一般会这么写,反而能避免问题出现:
setContent {
var padding1 by remember { mutableStateOf(8.dp) }
val modifier1 = Modifier.padding(padding1).clickable { padding1 = 0.dp }
var padding2 by remember { mutableStateOf(8.dp) }
val modifier2 = Modifier.padding(padding2).clickable { padding2 = 0.dp }
Column {
// Box 和 Text 分别使用不同的 modifier,修改不同的状态
Box(Modifier.background(Color.Blue) then modifier1)
Text("vincent", Modifier.background(Color.Green) then modifier2)
}
}
实际上 Modifier.composed() 的使用场景是,当我们需要创建的 Modifier 需要给它添加一些内部状态,这时候我们需要使用 Modifier.composed() 来为它提供一个 Composable 的上下文环境,从而让我们可以使用 remember;最终目的是让 Modifier 要能使用 remember 具备内部状态。而且因为 Modifier.composed() 是使用工厂函数延迟创建的,每调用一次就创建一次,所以也具备了每个 Composable 调用它时状态都是独立的。
fun Modifier.paddingModifier() = composed {
var padding by remember { mutableStateOf(8.dp) }
Modifier.padding(padding).clickable { padding = 0.dp }
}
因为 Modifier.composed() 的工厂函数提供了 Composable 上下文环境,所以没有对状态有需求,它也能使用在需要 Composable 环境的 Modifier 的创建。比如 LaunchedEffect、CompositionLocal 等:
fun Modifier.coroutineModifier() = composed {
LaunchedEffect(...) {
...
}
...
Modifier
}
fun Modifier.localModifier() = composed {
LocalContext.current
Modifier
}
不过 大多数时候 Modifier.composed() 都会用在需要内部状态使用 remember 的场景。
简单总结下 ComposedModifier 或 Modifier.composed():
-
按照官方注释的说法,Modifier.composed() 能创建出带有状态的 Modifier,让这个 Modifier 在多处被重用
-
有状态的 Modifier 就是被 remember 包着的变量和 Modifier 放在一起作为它的内部状态使用
-
重用就是 Modifier.composed() 在每一个使用的地方都创建一个内部状态独立的 Modifier,而不会互相影响
-
Modifier.composed() 不仅能提供内部状态,在一些需要 Composable 上下文环境例如 LaunchedEffect 或 CompositionLocal 等地方使用 Modifier,也可以使用它