Composable 的测量布局是使用的 LayoutModifier,剩下还有绘制流程,Compose 同样提供了一个 DrawModifier 处理绘制流程。比如 Modifier.background() 就是使用的 DrawModifier。
在讲解 DrawModifier 前,你觉得下面的结果会是什么:
Box(Modifier.requiredSize(80.dp).background(Color.Blue).requiredSize(40.dp))
最终结果:
40dp 蓝色的 Box()
上面的结果是显示一个 40dp 的 Box(),background() 会按右边的 Modifier 作为自己的显示区域。这是为什么呢?我们带着疑问开始了解 DrawModifier。
Compose 创建 LayoutModifier 提供了 Modifier.layout(),同样的要创建 DrawModifier 也提供了 Modifier.drawWithContent()。
Modifier.drawWithContent {
// 填写绘制内容
}
也许你看到这个函数名称会有疑问:为什么不像 LayoutModifier 那样是 Modifier.draw() 而是 Modifier.drawWithContent()?
这其实是 Compose 在强调你这个处理是在原有内容的基础上做的绘制过程,如果你不在 Modifier.drawWithContent() 填写内容,它将会把原有内容擦除。
什么叫原有内容?原有内容就是 DrawModifier 后面接上的 DrawModifier。写个例子方便理解:
setContent {
Box(Modifier.size(40.dp).background(Color.Blue).drawWithContent { })
}
上面的例子因为 Modifier.drawWithContent() 后面没有其他 DrawModifier,所以最终显示效果是 40dp 的 Box()。
现在我们把处理顺序换一下:
setContent {
// Modifier.drawWithContent() 没有填写内容
// 会将 background() 原有内容擦除
Box(Modifier.size(40.dp).drawWithContent { }.background(Color.Blue))
}
这次 Modifier.drawWithContent() 后面有一个 background() 的 DrawModifier,background() 是 Modifier.drawWithContent() 原有的内部内容,在调用 Modifier.drawWithContent() 但没有填写任何内容,这将会擦除 background()。所以最终显示效果是空白的。
如果想正常显示绘制内容,你应该在 Modifier.drawWithContent() 手动加上 drawContent():
setContent {
Box(Modifier.size(40.dp).drawWithContent { drawContent() }.background(Color.Blue))
}
drawContent() 的作用就是绘制内部的内容,它只能在 ContentDrawScope 上下文调用,在源码可以简单的看出来:
DrawModifier.kt
fun Modifier.drawWithContent(
onDraw: ContentDrawScope.() -> Unit
): Modifier = this.then(
DrawWithContentModifier(
onDraw = onDraw,
inspectorInfo = debugInspectorInfo {
name = "drawWithContent"
properties["onDraw"] = onDraw
}
)
)
private class DrawWithContentModifier(
val onDraw: ContentDrawScope.() -> Unit,
inspectorInfo: InspectorInfo.() -> Unit
) : DrawModifier, InspectorValueInfo(inspectorInfo) {
override fun ContentDrawScope.draw() {
// drawWithContent() 的 lambda
onDraw()
}
...
}
interface ContentDrawScope : DrawScope {
fun drawContent()
}
分析到这里可以先回答为什么不是 Modifier.draw() 而是 Modifier.drawWithContent(),因为使用 Modifier.drawWithContent() 是在强调我们要手动调用 drawContent(),否则会将原有内容擦除。
这个分析也能过侧面证明 DrawModifier 并不是用于增加绘制内容,而是用于替换绘制内容的。如果要保留绘制内容,你要手动调用 drawContent()。
Compose 不帮我们调用 drawContent() 也很好理解,这对应的就有更好的自由度,我们新加的代码可以填写在 drawContent() 前面或者后面,自定义在原有内容前后增加新的绘制内容:
Modifier.drawWithContent {
// 比原有内容先绘制...
drawContent()
// 比原有内容后绘制...
}
那么 Modifier.drawWithContent() 是怎么干预绘制流程的?接下来开始分析源码。
有关 Modifier 整体源码的分析都在讲解 LayoutModifier 提及,建议可以先看 LayoutModifier 对这部分源码的分析,相关的分析不会继续赘述。
LayoutNode.kt
override var modifier: Modifier = Modifier
set(value) {
...
nodes.updateFrom(value)
...
}
NodeChain.kt
internal class NodeChain(val layoutNode: LayoutNode) {
internal fun updateFrom(m: Modifier) {
...
val before = current ?: mutableVectorOf()
val after = m.fillVector(buffer ?: mutableVectorOf())
if (after.size == before.size) {
...
} else if (before.size == 0) {
...
var i = after.size - 1
var aggregateChildKindSet = 0
var node = tail
while (i >= 0) {
val next = after[i]
val child = node
// 创建链表节点
node = createAndInsertNodeAsParent(next, child)
// 记录已经添加到链表的 Modifier 标记并更新
aggregateChildKindSet = aggregateChildKindSet or node.kindSet
node.aggregateChildKindSet = aggregateChildKindSet
i--
}
}
...
}
private fun createAndInsertNodeAsParent(
element: Modifier.Element,
child: Modifier.Node,
): Modifier.Node {
val node = if (element is ModifierNodeElement<*>) {
element.create().also {
// 记录 Modifier 类型标记
it.kindSet = calculateNodeKindSetFrom(it)
}
} else {
BackwardsCompatNode(element)
}
// 将节点插入链表
return insertParent(node, child)
}
private fun insertParent(node: Modifier.Node, child: Modifier.Node): Modifier.Node {
// Modifier.Node 也是一个双向链表,使用的头插法将最新的 Modifier 插入到前面
// 假设 node 是 DrawModifier2,child 是 DrawModifier1
// 经过以下操作后,node = DrawModifier2->DrawModifier1
// 再添加 DrawModifier3,就是 node = DrawModifier3->DrawModifier2->DrawModifier1
val theParent = child.parent
if (theParent != null) {
theParent.child = node
node.parent = theParent
}
child.parent = node
node.child = child
return node
}
}
NodeKind.kt
// 每种 Modifier 类型对应一个标记位
internal object Nodes {
val Any = NodeKind<Modifier.Node>(0b1)
val Layout = NodeKind<LayoutModifierNode>(0b1 shl 1)
val Draw = NodeKind<DrawModifierNode>(0b1 shl 2)
val Semantics = NodeKind<SemanticsModifierNode>(0b1 shl 3)
val PointerInput = NodeKind<PointerInputModifierNode>(0b1 shl 4)
val Locals = NodeKind<ModifierLocalNode>(0b1 shl 5)
val ParentData = NodeKind<ParentDataModifierNode>(0b1 shl 6)
val LayoutAware = NodeKind<LayoutAwareModifierNode>(0b1 shl 7)
val GlobalPositionAware = NodeKind<GlobalPositionAwareModifierNode>(0b1 shl 8)
val IntermediateMeasure = NodeKind<IntermediateLayoutModifierNode>(0b1 shl 9)
// ...
}
internal fun calculateNodeKindSetFrom(element: Modifier.Element): Int {
var mask = Nodes.Any.mask
if (element is LayoutModifier) {
mask = mask or Nodes.Layout
}
if (element is IntermediateLayoutModifier) {
mask = mask or Nodes.IntermediateMeasure
}
if (element is DrawModifier) {
mask = mask or Nodes.Draw
}
if (element is SemanticsModifier) {
mask = mask or Nodes.Semantics
}
if (element is PointerInputModifier) {
mask = mask or Nodes.PointerInput
}
if (
element is ModifierLocalConsumer ||
element is ModifierLocalProvider<*> ||
// Special handling for FocusOrderModifier -- we have to use modifier local
// consumers and providers for it.
element is FocusOrderModifier
) {
mask = mask or Nodes.Locals
}
if (element is OnGloballyPositionedModifier) {
mask = mask or Nodes.GlobalPositionAware
}
if (element is ParentDataModifier) {
mask = mask or Nodes.ParentData
}
if (
element is OnPlacedModifier ||
element is OnRemeasuredModifier ||
element is LookaheadOnPlacedModifier
) {
mask = mask or Nodes.LayoutAware
}
return mask
}
updateFrom() 会将 Modifier 转换成 Modifier.Node(更具体的说是 DrawModifierNode),Modifier.Node 也是一个双向链表,当有相同类型的 Modifier 时,会将新的 Modifier 插入到 Modifier.Node 链表头;在需要访问所有 DrawModifier 时,就只需要拿到链表头的 DrawModifier 即可。
记录了 DrawModifier,接下来就是绘制流程怎么去使用这些 DrawModifier。
绘制流程是发生在 LayoutNode 的 draw:
LayoutNode.kt
internal val nodes = NodeChain(this)
internal val innerCoordinator: NodeCoordinator
get() = nodes.innerCoordinator
internal val layoutDelegate = LayoutNodeLayoutDelegate(this)
internal val outerCoordinator: NodeCoordinator
get() = nodes.outerCoordinator
internal fun draw(canvas: Canvas) = outerCoordinator.draw(canvas)
NodeCoordinator.kt
fun draw(canvas: Canvas) {
// layer 它是一个独立绘制的图层,它只是在一块额外的区域绘制而已,大多数时候是没有的
val layer = layer
if (layer != null) {
layer.drawLayer(canvas)
} else {
val x = position.x.toFloat()
val y = position.y.toFloat()
canvas.translate(x, y)
// 主要看这句代码
drawContainedDrawModifier(canvas)
canvas.translate(-x, -y)
}
}
private fun drawContainedDrawModifier(canvas: Canvas) {
// 拿到 DrawModifier 的链表头
val head = head(Nodes.Draw)
if (head == null) {
// head == null 说明没有设置过 DrawModifier
performDraw(canvas)
} else {
// 有设置过 DrawModifier,走 DrawScope.draw()
val drawScope = layoutNode.mDrawScope
drawScope.draw(canvas, size.toSize(), this, head)
}
}
LayoutNode 的 draw() 绘制会区分两种情况:
-
如果没有获取到 DrawModifier 的链表头,说明没有添加 DrawModifier,调用 performDraw()
-
如果能获取到 DrawModifier 的链表头,说明有添加 DrawModifier,调用 DrawModifier 的 draw()
我们先看第一种情况没有拿到 DrawModifier 调用 performDraw():
open fun performDraw(canvas: Canvas) {
wrapped?.draw(canvas)
}
performDraw() 只是用 wrapped 调用 draw(),wrapped 它是可空的即有可能不会绘制。该怎么理解呢?wrapped 其实会去找内部的 DrawModifier,如果找不到就为 null 不绘制。用一个简单的例子说明下:
Box(Modifier.padding(8.dp).size(40.dp))
layoutDelegate.outerCoordinator =
LayoutModifierNodeCoordinator( // size(40.dp)
SizeModifier,
LayoutModifierNodeCoordinator( // padding(8.dp)
PaddingModifier,
InnerNodeCoordinator
)
)
-
size(40.dp) 的 LayoutModifierNodeCoordinator 没有设置 DrawModifier,就去找内部有没有设置 DrawModifier
-
padding(8.dp) 也不是 DrawModifier,再继续往内部找 DrawModifier
-
最后到 InnerNodeCoordinator,没有设置 DrawModifier,所以就不绘制
使用的 Button()、Text() 这些 Composable 内部都是用 DrawModifier 来绘制的,并不存在它们“自带的绘制算法”,它们能显示内容就是因为有 DrawModifier,否则就只有测量布局,没有绘制。
现在看第二种情况能拿到 DrawModifier 调用 DrawScope.draw():
LayoutNode.kt
internal val mDrawScope: LayoutNodeDrawScope
get() = requireOwner().sharedDrawScope
这里的 mDrawScope 是 LayoutNodeDrawScope,也就是看 LayoutNodeDrawScope 的 draw():
LayoutNodeDrawScope.kt
internal inline fun draw(
canvas: Canvas,
size: Size,
coordinator: NodeCoordinator,
drawNode: DrawModifierNode,
) {
val previousDrawNode = this.drawNode // 临时将 DrawModifierNode 拿出来
this.drawNode = drawNode
// 主要看这里
canvasDrawScope.draw(
coordinator,
coordinator.layoutDirection,
canvas,
size
) {
with(drawNode) {
this@LayoutNodeDrawScope.draw()
}
}
this.drawNode = previousDrawNode // 处理完又还原回去
}
CanvasDrawScope.kt
inline fun draw(
density: Density,
layoutDirection: LayoutDirection,
canvas: Canvas,
size: Size,
block: DrawScope.() -> Unit
) {
...
canvas.save()
this.block() // 最终是调用的函数类型参数
canvas.restore()
...
}
DrawScope.draw() 主要做两件事情:
-
执行 block 前在内部临时把自己设置为 DrawModifierNode
-
执行 block 即函数类型参数的代码
block 函数类型参数是在 DrawModifier 上下文环境调用的 draw():
DrawModifier.kt
@JvmDefaultWithCompatibility
interface DrawModifier : Modifier.Element {
fun ContentDrawScope.draw()
}
我们手动添加的 Modifier.drawWithContent{} 的 lambda 也是在 draw() 调用处理绘制,如果是添加了 Modifier.background(),那么就是调用 background() 这个 DrawModifier 的 draw() 绘制自身。
我们在讲 Modifier.drawWithContent {} 有提到,如果要保留之前绘制的内容,就必须手动加上 drawContent() 的调用,现在开始分析 drawContent():
LayoutNodeDrawScope.kt
override fun drawContent() {
drawIntoCanvas { canvas ->
val drawNode = drawNode!!
val nextDrawNode = drawNode.nextDrawNode()
// 每个 DrawModifier 绘制如果没调用 drawContent(),就会导致绘制链条断开
if (nextDrawNode != null) {
// 还有下一个 DrawModifier,执行下一个 DrawModifier 的绘制
nextDrawNode.performDraw(canvas)
} else {
// 如果只有链表头一个 DrawModifier 或没有更多 DrawModifier
// 就让下一个节点的 NodeCoordinator 开始绘制
val coordinator = drawNode.requireCoordinator(Nodes.Draw)
val nextCoordinator = if (coordinator.tail === drawNode)
coordinator.wrapped!!
else
coordinator
nextCoordinator.performDraw(canvas)
}
}
}
DrawScope.kt
inline fun DrawScope.drawIntoCanvas(block: (Canvas) -> Unit) = block(drawContext.canvas)
drawIntoCanvas() 只是将函数类型参数传了 Canvas 参数后直接返回。
drawContent() 的处理分两种情况:
-
如果 DrawModifier 链表的下一个节点不为 null,说明还有 DrawModifier,从 DrawModifier 链表头开始遍历调用 DrawModifier 处理绘制
-
如果 DrawModifier 链表的下一个节点为 null,说明只有一个 DrawModifier 链表头或没有更多 DrawModifier,让下一个节点的 NodeCoordinator 开始绘制
从 drawContent() 的源码分析也可以理解,每个 DrawModifier 绘制如果没调用 drawContent(),就会导致绘制链条断开,不会再继续后续节点的绘制。
为了更好的理解,用简单的案例来帮助我们了解 drawContent() 的处理过程:
Box(Modifier.padding(8.dp).size(40.dp))
// 伪代码
LayoutModifierNodeCoordinator1(
PaddingModifier, // padding(8.dp)
[DrawModifier1, null, null, null, null, null, null, null]
LayoutModifierNodeCoordinator2( <- drawNode.requireCoordinator // 下一个绘制节点
SizeModifier, // size(40.dp)
[null, null, null, null, null, null, null, null]
InnerNodeCoordinator(
[null, null, null, null, null, null, null, null]
)
)
)
上面是对 Box() 只添加了一个 padding(8.dp) 的 DrawModifier,具体处理流程如下:
-
DrawModifier1.draw() 调用绘制,DrawModifier 链表没有下一个 DrawModifier,继续下一个节点 LayoutModifierNodeCoordinator2 查找有没有 DrawModifier
-
LayoutModifierNodeCoordinator2 没有 DrawModifier,继续下一个节点 InnerNodeCoordinator 查找有没有 DrawModifier
-
InnerNodeCoordinator 没有 DrawModifier,绘制结束
再看另一个案例加深理解:
LayoutModifierNodeCoordinator1(
PaddingModifier,
[DrawModifier1 -> DrawModifier2, null, null, null, null, null, null]
LayoutModifierNodeCoordinator2(
SizeModifier,
[DrawModifier3, null, null, null, null, null, null]
InnerNodeCoordinator(
[DrawModifier4 -> DrawModifier5 -> DrawModifier6, null, null, null, null, null, null]
)
)
)
-
DrawModifier1.draw() 调用绘制,DrawModifier 链表还有下一个节点调用 DrawModifier2.draw() 绘制,继续下一个节点 LayoutModifierNodeCoordinator2 查找有没有 DrawModifier
-
DrawModifier3.draw() 调用绘制,DrawModifier 链表没有下一个 DrawModifier,继续下一个节点 InnerNodeCoordinator 查找有没有 DrawModifier
-
以此类推调用 DrawModifier4.draw()、DrawModifier5.draw()、DrawModifier6.draw(),绘制结束
drawContent() 是通知当前 DrawModifier 下一级的 NodeCoordinator 的 DrawModifier 处理绘制。所以如果有一个 DrawModifier 没有调用 drawContent(),相当于链条断开不会通知内部的 DrawModifier 去绘制,比如到 DrawModifier4 没有调用 drawContent(),那么 DrawModifier5、DrawModifier6 就不绘制。
DrawModifier 除了链表头,其他链表节点的 DrawModifier 都在上一级 DrawModifier 的 drawContent() 调用时才开始绘制。如下伪代码:
// 伪代码
DrawModifier1.draw() {
drawContent() {
DrawModifier2.draw() {
drawContent() {
DrawModifier3.draw() {
...
}
}
}
}
}
至此可以解答一开始提出的问题:
Box(Modifier.background(Color.Red).background(Color.Blue))
最终是蓝色的 Box()
上面的最终结果是蓝色的 Box()。在 NodeChain 的 updateFrom() 是从右往左遍历的 Modifier,即先走的 background(Color.Blue) 然后是 background(Color.Red),因为 DrawModifier 链表是头插链表节点的,所以按程序的编写顺序,background(Color.Red) 是插入在 background(Color.Blue) 前的节点:
// 伪代码
background(Color.Red) {
drawContent() {
// 先绘制了 background(Color.Red) 背景
// background(Color.Blue) 盖住了 background(Color.Red)
// 它往下已经没有下一级 DrawModifier,所以最终显示蓝色
background(Color.Blue)
}
}
所以当程序从左往右写 DrawModifier,效果就是先写的 background(Color.Red) 先绘制在底部,后写的 background(Color.Blue) 绘制会盖住 background(Color.Red)。
Box(Modifier.requiredSize(80.dp).background(Color.Blue).requiredSize(40.dp)
最终显示 40dp 的蓝色 Box()
Box(Modifier.background(Color.Red).requiredSize(80.dp).background(Color.Blue).requiredSize(40.dp))
最终结果是 80dp 的红色方块上面有一个 40dp 的蓝色方块
Box() 大小是取决于 NodeCoordinator 的尺寸(LayoutModifierNodeCoordinator/InnerNodeCoordinator),要想知道绘制多大,本质上是要知道归属于左边还是右边所在的那个 NodeCoordinator。
这种场景当程序从左往右写 DrawModifier,DrawModifier 会和距离它右边最近的 LayoutModifier 作为决定它的绘制尺寸。