为什么说 Modifier
是万能的;因为它可以代替 View 上的常用的 padding
、margin
、width
、height
等对 View
属性的设置,同时也可以添加 click
、longClick
、scroll
等监听事件
在 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 个实现
Modifier(Companion)
的实现;因为Modifier(Companion)
是一个空实现,所以直接返回other
的Modifier
对象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
}
)
)
步骤讲解
Modifier.background(Color.Pink)
因当前 Modifier 为 Companion 对象,所以执行后表达式的值为BackgroundElement
- 后续步骤通过上述源码可知,都是通过
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
:
LayoutModifier
及其子类- 非
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
维护 LayoutNode
的 node 链 和 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
- 其中
MutableVector
当成一个ArrayList
即可;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的颜色间距的