Compose编程思想 -- Modifier基础知识

相关文章:

Compose编程思想 -- 初识Compose

在更新的第一篇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传入的Backgroundsize传入的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的组成:

graph LR background --> size --> clickable

如果使用foldIn,那么就会从左向右正序输出;如果使用foldOut,那么就会从右向左倒序输出。

使用anyall更为简单,因为两者均返回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来看,整体的链路调用:

graph LR Box --> Layout --> Layout#materializerOf --> Composer#materialize

最终会执行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扩展函数内部进行重组,不影响外部。

相关推荐
SRC_BLUE_171 小时前
SQLI LABS | Less-39 GET-Stacked Query Injection-Intiger Based
android·网络安全·adb·less
无尽的大道4 小时前
Android打包流程图
android
镭封6 小时前
android studio 配置过程
android·ide·android studio
夜雨星辰4876 小时前
Android Studio 学习——整体框架和概念
android·学习·android studio
邹阿涛涛涛涛涛涛6 小时前
月之暗面招 Android 开发,大家快来投简历呀
android·人工智能·aigc
IAM四十二6 小时前
Jetpack Compose State 你用对了吗?
android·android jetpack·composer
奶茶喵喵叫7 小时前
Android开发中的隐藏控件技巧
android
Winston Wood9 小时前
Android中Activity启动的模式
android
众乐认证9 小时前
Android Auto 不再用于旧手机
android·google·智能手机·android auto
三杯温开水9 小时前
新的服务器Centos7.6 安卓基础的环境配置(新服务器可直接粘贴使用配置)
android·运维·服务器