相关文章:
在更新的第一篇Compose文章时,我简单介绍了Modifier的使用,当前给出的解释就是:Modifier主要用来处理公共逻辑,例如设置背景、设置宽高、设置监听等。
kotlin
@Composable
fun Text(
text: String,
// Modifier成员变量
modifier: Modifier = Modifier,
// Text独有属性
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
// ......
) {
如果仔细看@Composable
函数的源码,我们都能看到在函数中会提供一个modifier参数,并将其初始化为Modifier。
kotlin
/**
* The companion object `Modifier` is the empty, default, or starter [Modifier]
* that contains no [elements][Element]. Use it to create a new [Modifier] using
* modifier extension factory functions:
*
* @sample androidx.compose.ui.samples.ModifierUsageSample
*
* or as the default value for [Modifier] parameters:
*
* @sample androidx.compose.ui.samples.ModifierParameterSample
*/
// The companion object implements `Modifier` so that it may be used as the start of a
// modifier extension factory expression.
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的任意扩展函数。
例如background
属性就是Modifier的一个扩展函数,通过Modifier链式调用配置组件的属性。
kotlin
fun Modifier.background(
color: Color,
shape: Shape = RectangleShape
) = this.then(
Background(
color = color,
shape = shape,
inspectorInfo = debugInspectorInfo {
name = "background"
value = color
properties["color"] = color
properties["shape"] = shape
}
)
)
1 Modifier基础知识
1.1 Composable函数中的Modifier参数
既然官方的每一个@Composable
函数都有一个Modifier参数,那么我们自己定义的@Composable
函数未来也会向外界提供使用,所以也需要添加一个Modifier参数,用于配置自定义的@Composable
函数。
kotlin
@Composable
fun ShowBackground(modifier: Modifier = Modifier) {
// 注意这里调用的方式!!!
Box(modifier.background(Color.Blue)) {
Text(text = "检测文案展示", modifier = Modifier.background(LocalBackground.current))
}
}
例如ShowBackground
函数中我添加了一个Modifier参数,这个参数可以用于外界传入配置,以便配置Box
的属性,那么在Box
使用Modifier的时候,只能拿参数中的modifier,不能再新建一个Modifier,保证与外界的配置联系到一起。
kotlin
CompositionLocalProvider(LocalBackground provides Color.Blue) {
// 外界配置了Box的宽高为200dp
ShowBackground(Modifier.size(200.dp))
}
1.2 Modifier链式调用源码分析
前面我们介绍了Modifier可以采用链式调用的方式组合一个Modifier对象,我们从源码出发看下Modifier如何完成链式调用的。
kotlin
object Glide {
fun with(context: Context) = apply {
}
fun load(url: String) = apply {
}
fun into(view: ImageView) = apply {
}
}
对于链式调用,我们常见的像建造者模式中通过链式调用创建一个对象;还有像Glide使用链式调用加载图片,其实能够实现链式调用,就是在每个方法中都返回当前对象实例即可。
那么Modifier也是采用这种方式实现的,我们还是看background
扩展函数来分析。
kotlin
fun Modifier.background(
color: Color,
shape: Shape = RectangleShape
) = this.then(
Background(
color = color,
shape = shape,
inspectorInfo = debugInspectorInfo {
name = "background"
value = color
properties["color"] = color
properties["shape"] = shape
}
)
)
在调用background
函数之后,返回值是调用了Modifier的then
函数,这个函数不是扩展函数,需要传入一个Modifier参数,但最终返回的是一个Modifier对象,这也满足了前面我们提到的链式调用规则。
kotlin
/**
* Concatenates this modifier with another.
*
* Returns a [Modifier] representing this modifier followed by [other] in sequence.
*/
infix fun then(other: Modifier): Modifier =
if (other === Modifier) this else CombinedModifier(this, other)
接下来我要分析一下then
函数,如果传进来的Modifier是白板(即空的伴生对象),那么直接返回当前调用的Modifier对象即可;
kotlin
Modifier
.background(Color.Blue)
.then(Modifier)
// 上式等价于 ↓
Modifier.background(Color.Blue)
如果传入的对象不是白板,那么就调用CombinedModifier
将两个Modifier融合。
kotlin
Modifier
.background(Color.Blue)
.size(100.dp)
// 上式等价于
CombinedModifier(Modifier.background(Color.Blue), Modifier.size(100.dp))
其实在Compose中的Modifier链式调用过程中,最终都会执行Modifier的then
函数进行Modifier的融合,而传入的Modifier对象,例如backgroud
传入的Background
,size
传入的SizeModifier
等等,
kotlin
// Modifier Background
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) { // ...... }
kotlin
private class SizeModifier(
private val minWidth: Dp = Dp.Unspecified,
private val minHeight: Dp = Dp.Unspecified,
private val maxWidth: Dp = Dp.Unspecified,
private val maxHeight: Dp = Dp.Unspecified,
private val enforceIncoming: Boolean,
inspectorInfo: InspectorInfo.() -> Unit
) : LayoutModifier, InspectorValueInfo(inspectorInfo) { // ...... }
都是直接或者间接实现了Modifier.Element
接口。
kotlin
interface LayoutModifier : Modifier.Element { // ......}
1.3 Modifier.Element接口分析
前面我们提到,Compose中的Modifier都会直接或者间接实现了Modifier.Element
接口,对于这个接口官方给出的解释:
在Modifier链式对象中的单一成员
kotlin
/**
* A single element contained within a [Modifier] chain.
*/
@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)
}
所以如果我们自定义Modifier,那么一定要实现这个接口。Modifier.Element
接口中有4个成员函数, 因为只要在Modifier链中的Modifier.Element,都会通过CombinedModifier
组合在一起,因此看一下多个Modifier的组合项是如何执行这些函数。
kotlin
/**
* A node in a [Modifier] chain. A CombinedModifier always contains at least two elements;
* a Modifier [outer] that wraps around the Modifier [inner].
*/
class CombinedModifier(
internal val outer: Modifier, // 调用then函数的Modifier
internal val inner: Modifier // 后来加入的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"
} + "]"
}
- foldIn
用于正序遍历所有的Modifier Element元素;
- foldOut
用于倒序遍历所有的Modifier Element元素;
- any
any代表任意一个,如果有任意一个Element执行all
函数返回true,那么返回的结果就为true;因为每个Modifier都可能由多个Element组成,那么all函数会将每个Element都遍历到。
- all
all代表全部,如果每个Element执行all
函数的结果均为true,那么返回的结果就是true;
那么Modifier Element提供这几个方法的目的是为了干什么用呢?其实就是为了方便开发者在不清楚Modifier内部构成的前提下,获取全部的Modifier Element对象,并在其基础之上做业务逻辑。
kotlin
val modifier = Modifier
.background(Color.Blue)
.size(100.dp)
.clickable {
}
// 调用 foldOut 逆序输出全部的Modifier Element
modifier.foldOut(null) { element, _ ->
Log.d("TAG", "onCreate: $element")
return@foldOut null
}
看下Modifier的组成:
如果使用foldIn
,那么就会从左向右正序输出;如果使用foldOut
,那么就会从右向左倒序输出。
使用any
和all
更为简单,因为两者均返回boolean类型结果,但均为正向遍历的过程。那么在Compose中使用这种遍历的目的是为什么呢,或者说提供这些函数有什么用呢?在专题刚开始我们在介绍Modifier.clickable的时候,我们发现clickable放置的顺序不同会导致点击热区的不同,主要的原因就在于Modifier对顺序是敏感的,具体为什么敏感,我们后续的章节中会介绍。
1.4 ComposedModifier
ComposedModifier
是Compose当中一个比较特殊的Modifier,它是Modifier执行composed
函数的时候,会创建一个ComposedModifier。
kotlin
/**
* Declare a just-in-time composition of a [Modifier] that will be composed for each element it
* modifies. [composed] may be used to implement **stateful modifiers** that have
* instance-specific state for each modified element, allowing the same [Modifier] instance to be
* safely reused for multiple elements while maintaining element-specific state.
*
* If [inspectorInfo] is specified this modifier will be visible to tools during development.
* Specify the name and arguments of the original modifier.
*
* Example usage:
* @sample androidx.compose.ui.samples.InspectorInfoInComposedModifierSample
* @sample androidx.compose.ui.samples.InspectorInfoInComposedModifierWithArgumentsSample
*
* [materialize] must be called to create instance-specific modifiers if you are directly
* applying a [Modifier] to an element tree node.
*/
fun Modifier.composed(
inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo,
factory: @Composable Modifier.() -> Modifier
): Modifier = this.then(ComposedModifier(inspectorInfo, factory))
在composed
函数中,factory
参数用于构建一个Modifier对象,只有在组件中的modifier被使用的时候,才会执行factory
中的创建流程。
那么什么时候,组件中的Modifier才会被使用?
kotlin
Box(Modifier.composed {
Modifier
.background(Color.Blue)
.size(400.dp)
}) {
}
我在Box中声明了Modifier对象,当Box函数被执行的时候,会执行composed
函数创建一个ComposedModifier
对象,此时内部的Modifier(背景为蓝色、宽高为400dp)并没有被创建。
这里拿Box来看,整体的链路调用:
最终会执行Composer
的扩展函数materialize
,可以理解为将Modifier转换为material化。
kotlin
/**
* Materialize any instance-specific [composed modifiers][composed] for applying to a raw tree node.
* Call right before setting the returned modifier on an emitted node.
* You almost certainly do not need to call this function directly.
*/
@Suppress("ModifierFactoryExtensionFunction")
fun Composer.materialize(modifier: Modifier): Modifier {
if (modifier.all { it !is ComposedModifier }) {
return modifier
}
// This is a fake composable function that invokes the compose runtime directly so that it
// can call the element factory functions from the non-@Composable lambda of Modifier.foldIn.
// It would be more efficient to redefine the Modifier type hierarchy such that the fold
// operations could be inlined or otherwise made cheaper, which could make this unnecessary.
// Random number for fake group key. Chosen by fair die roll.
startReplaceableGroup(0x48ae8da7)
// 初始值就是Modifier白板
val result = modifier.foldIn<Modifier>(Modifier) { acc, element ->
// 返回的result就是acc,开始就是一个白板
acc.then(
if (element is ComposedModifier) {
@Suppress("UNCHECKED_CAST")
val factory = element.factory as Modifier.(Composer, Int) -> Modifier
val composedMod = factory(Modifier, this, 0)
materialize(composedMod)
} else {
element
}
)
}
endReplaceableGroup()
return result
}
首先执行了Modifier.Element的all
函数,判断所有的Modifier中是否存在ComposedModifier
,如果不存在,那么就将Modifier直接返回即可,所以从这里就说明了如果是ComposedModifier
就会做特殊处理。
如果Modifier中存在ComposedModifier
,那么就执行了foldIn
函数,开始正序遍历所有的Element,如果碰到ComposedModifier
,就会执行factory
函数,也就是执行composed
lambda表达式内部的Modifier的构建,构建完成之后,会再次调用Composer # materialize
函数,因为内部可能还会嵌套ComposedModifier
;如果不是ComposedModifier
,那么就很简单了,只需要跟调用then
的Modifier融合即可,最终返回的就是普通的Modifier与composed内部的Modifier融合之后 的Modifier。
那有啥用啊?为啥要用到这个composed
函数,来看下官方的解释:
1 可以实现一个有状态的Modifier
2 可以在多个地方被复用,它是状态独立的
何为有状态的Modifier,在之前的文章中,像一个@Composable
函数有内部状态,那么就认为这个组合函数有状态,所以Modifier也是一样的,例如:
kotlin
Box(Modifier.composed {
// size的状态
val size by remember {
mutableStateOf(50.dp)
}
Modifier
.background(Color.Blue)
.size(size)
}) {
}
这里我只是举一个例子,将size
的值通过mutableStateOf
来装饰,这样生成的Modifier就是有状态的,例如我们设置一个监听:
kotlin
Box(Modifier.composed {
var size by remember {
mutableStateOf(50.dp)
}
Modifier
.background(Color.Blue)
.size(size).clickable {
size = 100.dp
}
}) {
}
当点击box的时候,将size变为100,就可以实现布局的动态变化,对于普通的Modifier来说,也可以通过下面这种方式构建,但是一般不会说这个Modifier有状态,而是其所在的组合作用域是有状态的。
kotlin
val size by remember {
mutableStateOf(50.dp)
}
val modifier = Modifier
.size(size)
.background(Color.Blue)
而对于状态独立 ,composed
有它天然的优势,我们先看一个例子:
kotlin
var size by remember {
mutableStateOf(50.dp)
}
val modifier = Modifier
.width(size)
.background(Color.Blue)
.clickable {
size = 100.dp
}
Column {
Text(text = "BOX III", modifier)
Spacer(modifier = Modifier.height(20.dp))
Text(text = "另一个Text", modifier)
}
例如两个Text
复用同一个Modifier,当任意一个组件将size的值做修改之后,其他组件的Modifier也会发生变化,导致出现bug。
而composed
因为其内部实现的过程中,每次都会通过Composer#materialize
函数,从一个Modifier白板开始构建一个新的Modifier对象,因此不存在共用一个Modifier的情况,也是就官方文档中提到的,可以安全的在多个地方复用Modifier。
kotlin
val modifier =
Modifier.composed {
var size by remember {
mutableStateOf(50.dp)
}
Modifier
.width(size)
.background(Color.Blue)
.clickable {
size = 100.dp
}
}
Column {
Text(text = "BOX III", modifier)
Spacer(modifier = Modifier.height(20.dp))
Text(text = "另一个Text", modifier)
}
那么在实际的开发中,我们会这么写吗?虽然可以炫技,但是可读性不高,而且我们也无法保证不会出问题,更多的时候,我们会针对每一个组件单独创建一个Modifier,同样也可以实现状态独立,而且可读性更高。
所以composed
的用处到底是什么呢?其实核心就是用于创建带状态的Modifier,常用于自定义Modifier。
kotlin
/**
* 用于膨胀宽度
*/
fun Modifier.expandWidth(size: Dp) = composed {
// 点击组件
var statefulSize by remember {
mutableStateOf(size)
}
Modifier
.width(statefulSize)
.clickable {
statefulSize = 100.dp
}
}
因为remember
只能用到@Composable
函数中,因此使用composed
就可以解决这个问题,使得Modifier带状态。所以记住一个准则,当要在自定义Modifier中使用到Composable函数时,套一层composed函数即可。
kotlin
Column {
Text(text = "BOX III",
Modifier
.background(Color.Blue)
.expandWidth(20.dp))
Spacer(modifier = Modifier.height(20.dp))
Text(text = "另一个Text",
Modifier
.background(Color.Blue)
.expandWidth(20.dp))
}
所以在使用的时候,因为Modifier自身带状态,所以点击组件的时候,只有在Modifier扩展函数内部进行重组,不影响外部。