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的颜色间距的

相关推荐
qq_12498707532 小时前
Android+SpringBoot的老年人健康饮食小程序平台
android·spring boot·小程序·毕业设计
daily_23335 小时前
c++领域展开第十四幕——STL(String类的常用接口说明以及相关练习)超详细!!!!
android·开发语言·c++
SunshineBrother5 小时前
Flutter性能优化细节
android·flutter·ios
居然是阿宋7 小时前
Java/Kotlin 开发者如何快速入门 C++
java·c++·kotlin
daily_23339 小时前
c++领域展开第十五幕——STL(String类的模拟实现)超详细!!!!
android·开发语言·c++
m0_7482350718 小时前
【MySQL】数据库开发技术:内外连接与表的索引穿透深度解析
android·mysql·数据库开发
werch20 小时前
兼容移动端ios,安卓,web端底部软键盘弹出,输入框被遮挡问题
android·前端·ios
alexhilton21 小时前
不使用Jetpack Compose的10个理由
android·kotlin·android jetpack
峥嵘life21 小时前
Android14 串口控制是能wifi adb实现简介
android·adb