Compose UI 中万能的 Modifier

为什么说 Modifier 是万能的;因为它可以代替 View 上的常用的 paddingmarginwidthheight 等对 View 属性的设置,同时也可以添加 clicklongClickscroll 等监听事件

Compose 中,除了 composable 函数专有属性外(如 Text 中的 text 属性、Image 中的 painter 属性),其余 对 composable 通用设置 都是在 Modifier 中进行设置;所以掌握 Modifier 的原理,对于我们使用 Compose 来绘制 UI 效率有直接关系

Modifier 继承树

我们先来看下 Modifier 的继承树,与 CoroutineContext 继承树类似

两个图放在一起看,属于同一设计理念

Modifier 类的说明

  • Modifier(interface) 是一个接口,用于定义了 Modifier 通用属性和方法
  • Modifier(companion) 是一个单例,方便带出其他 Modifier ,如 Modifier.padding(),本身是 Modifier空实现
  • CombinedModifier(class) 是一个类,用于连接 Modifier,从而形成一个链表结构
  • Element(interface) 是一个接口,是 Modifier 实际干活的节点,是所有 干活 Modifier 的父类

Modifier 链式调用

在看 Modifier 链式调用之前,我们先来看下 Modifier.then 的实现

then 的实现

kotlin 复制代码
interface Modifier {

    //infix 是一个 Kotlin 修饰符,使用这修饰符可以使 modifier.then(SizeElement()) 修改为 modifier then SizeElement
    infix fun then(other: Modifier): Modifier = 
        if (other === Modifier) this else CombinedModifier(this, other)  

    companion object : Modifier {
        // Modifier(companion) 是一个空实现,会直接返回 then
        override infix fun then(other: Modifier): Modifier = other
    }
}

class CombinedModifier(
    internal val outer: Modifier,
    internal val inner: Modifier
)

then 函数有 2 个实现

  1. Modifier(Companion) 的实现;因为 Modifier(Companion) 是一个空实现,所以直接返回 otherModifier 对象
  2. Modifier(interface) 做了相等处理,不相等则直接使用 CombinedModifier 连接 2 个 Modifier;

Modifier 的链式调用

下面都以 Modifier.background(Color.PINK).padding(10.dp).background(Color.CYAN).size(30.dp) 为例

kotlin 复制代码
// androidx.compose.foundation.Background.kt
@Stable
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
            }
        )
    )
}

// androidx.compose.foundation.layout.Padding.kt
@Stable
fun Modifier.padding(
    horizontal: Dp = 0.dp,
    vertical: Dp = 0.dp
) = this then PaddingElement(
    start = horizontal,
    top = vertical,
    end = horizontal,
    bottom = vertical,
    rtlAware = true,
    inspectorInfo = {
        name = "padding"
        properties["horizontal"] = horizontal
        properties["vertical"] = vertical
    }
)

// androidx.compose.foundation.layout.Size.kt
@Stable
fun Modifier.size(size: Dp) = this.then(
    SizeElement(
        minWidth = size,
        maxWidth = size,
        minHeight = size,
        maxHeight = size,
        enforceIncoming = true,
        inspectorInfo = debugInspectorInfo {
            name = "size"
            value = size
        }
    )
)

步骤讲解

  1. Modifier.background(Color.Pink) 因当前 Modifier 为 Companion 对象,所以执行后表达式的值为 BackgroundElement
  2. 后续步骤通过上述源码可知,都是通过 CombinedModifier 进行连接

Modifier.background(Color.PINK).padding(10.dp).background(Color.CYAN).size(30.dp) 最后的产物如下图:

Modifier 随心用

要想 Modifier 随心用,我们需要了解底层原理才能实现.

结论

「非 LayoutModifier 子类的效果」会作用到「右边最接近的 LayoutModifier 子类」

底层实现

以下的源码基于 compose - 1.7.2 版本

从上图中可知,Modifier.Element 有各种各样的子类;但实际上在 Compose 内部中只有 LayoutModifier 被区别对待,其余都是同一分类,按照此规则可以分为2类 Modifier

  1. LayoutModifier 及其子类
  2. LayoutModifier 及其子类

下面我们从最简单的 Box 开始追踪 Modifier 的赋值

kotlin 复制代码
@Composable
fun Box(modifier: Modifier) {
    // step1: 调用 Layout 函数
    Layout(measurePolicy = EmptyBoxMeasurePolicy, modifier = modifier)
}

@Composable
@UiComposable
inline fun Layout(
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {
    val compositeKeyHash = currentCompositeKeyHash
    
    // step2: ComposeModifier 的解包,即 Modifier.composed
    val materialized = currentComposer.materialize(modifier)
    val localMap = currentComposer.currentCompositionLocalMap
    
    // step3: 这里的 ComposeUiNode 是 LayoutNode,即调用的 LayoutNode.modifier set 方法
    ReusableComposeNode<ComposeUiNode, Applier<Any>>(
        factory = ComposeUiNode.Constructor,
        update = {
            set(measurePolicy, SetMeasurePolicy)
            set(localMap, SetResolvedCompositionLocals)
            // 将解包后的 materialized 传递给 LayoutNode.modifier 设置
            set(materialized, SetModifier)
            set(compositeKeyHash, SetCompositeKeyHash)
        },
    )
}

/* ------------------------- Modifier.composed 解析 ------------------------- */
fun Composer.materialize(modifier: Modifier): Modifier {
    startReplaceGroup(0x1a365f2c)
    val result = materializeImpl(modifier)
    endReplaceGroup()
    return result
}

private fun Composer.materializeImpl(modifier: Modifier): Modifier {
    // 如果当前没有 ComposedModifier 则不需要处理,直接返回
    if (modifier.all { it !is ComposedModifier }) {
        return modifier
    }
    startReplaceableGroup(0x48ae8da7)

    // foldIn 相当于是最里面的 outer 遍历最外面到 inner,即从左到右遍历
    val result = modifier.foldIn<Modifier>(Modifier) { acc, element ->
        acc.then(
            // 如果当前是 ComposedModifier 取解析,提供 compose 环境
            if (element is ComposedModifier) {
                val factory = element.factory as Modifier.(Composer, Int) -> Modifier
                val composedMod = factory(Modifier, this, 0)
                materializeImpl(composedMod)
            } else {
                element
            }
        )
    }
    endReplaceableGroup()
    return result
}


/* ------------------------- SetModifier ------------------------- */
@PublishedApi
internal interface ComposeUiNode {
    companion object {
        // CompioseUiNode 调用的是 LayoutNode.Constructor
        val Constructor: () -> ComposeUiNode = LayoutNode.Constructor
        // ComposeUiNode 调用的是 LayoutNode.modifier set
        val SetModfier: ComposeUiNode.(Modifier) -> Unit = { this.modifier = it }
    }
}

internal class LayoutNode(){
    internal companion object {
        // 直接执行 LayoutNode 的构造
        internal val Constructor: () -> LayoutNode = { LayoutNode() }
    }
}

LayoutNode.modifier Set 方法

kotlin 复制代码
internal class LayoutNode(
    private val isVirtual: Boolean = false,
    override var semanticsId: Int = generateSemanticsId()
) : ComposeNodeLifecycleCallback,
    Remeasurement,
    OwnerScope,
    LayoutInfo,
    ComposeUiNode,
    InteroperableComposeUiNode,
    Owner.OnLayoutCompletedListener {

    override var modifier: Modifier
        get() = _modifier
        set(value) {
            // ...
            if (isAttached) {
                // step1: 关联上调用 applyModifier
                applyModifier(value)
            } else {
                // ...
            }
        }

    private fun applyModifier(modifier: Modifier) {
        // step2: 存储当前新的 Modifier
        _modifier = modifier
        // step3: 使用新的 Modifier 更新 LayoutNode 的 NodeChain
        nodes.updateFrom(modifier)
        // ...
    }
}

NodeChain#updateFrom

updateFrom 根据传入的 Modifier 维护 LayoutNodenode 链Coordinator 链 ;这里会有多种情况,其中最简单的情况为全创建,我们针对此情况进行讲解

kotlin 复制代码
internal class NodeChain(val layoutNode: LayoutNode) {

    internal fun updateFrom(m: Modifier) {
        var coordinatorSyncNeeded = false

        // 增加一个链表头,用于防止内部 Modifier 全部被移除的情况
        val paddedHead = padChain()

        // 前一个 Modifier 的 node 大小
        var before = current
        val beforeSize = before?.size ?: 0

        // step1: 使用 buffer(Vector) 存储展开的 ModifierElement「只保留 Element 元素」
        val after = m.fillVector(buffer ?: mutableVectorOf())
        var i = 0
        if (after.size == beforeSize) {
            // 情况一:这里主要是对比 Modifier 节点的增删改查
        } else if (layoutNode.applyingModifierOnAttach && beforeSize == 0) {
            // 情况二:这里 Modifier 全部重新创建
            coordinatorSyncNeeded = true // 标记需要更新 coordinator

            // step2: 遍历 buffer 并将其中的 Modifier 转换为对应的 Node
            var node = paddedHead
            while (i < after.size) {
                val next = after[i]
                val parent = node
                // 使用尾插法把 next 插在 parent 的后面,这样顺序与 buffer 一致
                node = createAndInsertNodeAsChild(next, parent)
                i++
            }
            syncAggregateChildKindSet() // 同步当前节点的特征
        } else if (after.size == 0) {
            // 情况三:这里是 Modifier 全部删除
        } else {
            // 情况四:这里是 Modifier 的修改
        }

        current = after
        buffer = before?.also { it.clear() }
        
        // 移除 padChain 添加的链表头
        head = trimChain(paddedHead)
        
        if (coordinatorSyncNeeded) {
            // step3: 维护 Coordinator 链
            syncCoordinators()
        }
    }
}
fillVector
kotlin 复制代码
private fun Modifier.fillVector(
    result: MutableVector<Modifier.Element> // 当成一个 ArrayList 即可
): MutableVector<Modifier.Element> {
    val capacity = result.size.coerceAtLeast(16)

    // step1: BFS 一般使用的是队列,这里使用的是栈; 同时把根节点放进栈中
    val stack = MutableVector<Modifier>(capacity).also { it.add(this) }
    var predicate: ((Modifier.Element) -> Boolean)? = null

    // step2: BFS 标准写法
    while (stack.isNotEmpty()) {
        when (val next = stack.removeAt(stack.size - 1)) {
            // step3: 当前是 CombinedModifier 节点,则展开
            is CombinedModifier -> {
                stack.add(next.inner)
                stack.add(next.outer) // 最后放入的是 outer,则先解析的是 outer
            }
            // step4: 当前是 Element 节点,加入到 result 中
            is Modifier.Element -> result.add(next)
            // step5: 自定义 Modifier 则使用 all 函数取出所有 Element,并放入 result 中
            else -> next.all(predicate ?: { element: Modifier.Element ->
                result.add(element)
                true
            }.also { predicate = it })
        }
    }
    return result
}

Tips

  1. 其中 MutableVector 当成一个 ArrayList 即可;
  2. predicate 是针对自定义 Modifier,一般情况下不会执行;

fillVector 函数主要是把当前 Modifier 按照 从里往外规则 取出所有的 Element 元素; 使用的是 BFS(Breadth First Search) 算法;,其实与 Modifier.foldIn 一致;在 compose 1.5.0 之前也确实使用 foldIn 实现的;最后函数执行完成得到一个 result 如下图所示:

createAndInsertNodeAsChild
kotlin 复制代码
internal class NodeChain(val layoutNode: LayoutNode) {

    private fun createAndInsertNodeAsChild(
        element: Modifier.Element,
        parent: Modifier.Node,
    ): Modifier.Node {
        val node = when (element) {
            // 当前 Modifier.Element create 创建对应节点
            is ModifierNodeElement<*> -> element.create().also {
                // kindSet 是使用二进制的位存储当前 Node 的类型,用于后续快速查找是否存在某个 Node
                it.kindSet = calculateNodeKindSetFromIncludingDelegates(it)
            }
            // 用于兼容老版本 Compose
            else -> BackwardsCompatNode(element)
        }
        node.insertedNodeAwaitingAttachForInvalidation = true
        return insertChild(node, parent)
    }

    /**
     * 使用尾插法
     * before: Head... -> parent -> ...Tail
     * after: Head... -> parent -> node -> ...Tail
     */
    private fun insertChild(node: Modifier.Node, parent: Modifier.Node): Modifier.Node {
        val theChild = parent.child
        if (theChild != null) {
            theChild.parent = node
            node.child = theChild
        }
        parent.child = node
        node.parent = parent
        return node
    }
}

该函数主要是把当前的 Modifier 转换成对应的 Node 节点; 同时从注释可知,使用尾插法维护链表;最终生成的 Node 数据与原先 Element 的顺序一致;生成 Node 链如下图所示:

syncCoordinators 同步
kotlin 复制代码
internal class NodeChain(val layoutNode: LayoutNode) {

    // ...

    fun syncCoordinators() {
        // step1: 每个非内联 Composable 函数都有一个 innerCoordinator
        var coordinator: NodeCoordinator = innerCoordinator

        // step2: 从尾开始遍历
        var node: Modifier.Node? = tail.parent
        while (node != null) {
            // step3: 判断当前是 LayoutModifierNode - 这里把 LayoutModifier 特殊对待
            val layoutmod = node.asLayoutModifierNode() // 当前是 LayoutModfier 生成的 node
            if (layoutmod != null) { 

                // step4: 如果原来已经创建了,则修改;没有则创建
                val next = if (node.coordinator != null) {
                    val c = node.coordinator as LayoutModifierNodeCoordinator
                    val prevNode = c.layoutModifierNode
                    c.layoutModifierNode = layoutmod
                    if (prevNode !== node) c.onLayoutModifierNodeChanged()
                    c
                } else {
                    val c = LayoutModifierNodeCoordinator(layoutNode, layoutmod)
                    node.updateCoordinator(c)
                    c
                }

                // step5: coordinator 和 next 前后相连
                coordinator.wrappedBy = next
                next.wrapped = coordinator
                coordinator = next
            } else {
                //step6: 如果当前不是 Layout Modifier 的 Node,则记住当前的 NodeCoordinator
                node.updateCoordinator(coordinator)
            }
            node = node.parent
        }

        // step7: 最后一个与 LayoutNode 的 parent 相连,保证链条完整性
        coordinator.wrappedBy = layoutNode.parent?.innerCoordinator

        // step8: 当前的 coordinator 为融合 LayoutModifier 之后的
        outerCoordinator = coordinator
    }

    // ...

}

此函数主要是建立与布局相关的 Coordinator 链,按照上面举例子的 Modifier 最终生成如下图所示:

总结

由最后一张图可知,BackgroundNode(PINK) 指向了右边最接近的 LayoutModifierNode 的子类 PaddingNode(10.dp),同样 BackgroundNode(CYAN) 也是指向了右边最接近的 LayoutModifierNode 的子类 SizeNode(30.dp); Node 的指向代表当前使用 Coordinator 所在的位置和大小进行自身的装饰 ,所以最终效果为 30.dp 的 CYAN 颜色外面为 10.dp PINK的颜色间距的

相关推荐
似霰1 小时前
安卓adb shell串口基础指令
android·adb
fatiaozhang95273 小时前
中兴云电脑W102D_晶晨S905X2_2+16G_mt7661无线_安卓9.0_线刷固件包
android·adb·电视盒子·魔百盒刷机·魔百盒固件
louisgeek4 小时前
Kotlin 面试知识点
kotlin
CYRUS_STUDIO4 小时前
Android APP 热修复原理
android·app·hotfix
鸿蒙布道师4 小时前
鸿蒙NEXT开发通知工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
鸿蒙布道师4 小时前
鸿蒙NEXT开发网络相关工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
大耳猫5 小时前
【解决】Android Gradle Sync 报错 Could not read workspace metadata
android·gradle·android studio
ta叫我小白5 小时前
实现 Android 图片信息获取和 EXIF 坐标解析
android·exif·经纬度
dpxiaolong6 小时前
RK3588平台用v4l工具调试USB摄像头实践(亮度,饱和度,对比度,色相等)
android·windows
tangweiguo030519877 小时前
Android 混合开发实战:统一 View 与 Compose 的浅色/深色主题方案
android