Compose:DrawModifier

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 作为决定它的绘制尺寸

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值