本篇讲解 DrawModifier 的用法与原理,介绍原理的目的在于可以判断绘制与绘制的关系,绘制与布局的关系。知道达成某种绘制效果应该怎么写,以及面对复杂的 Modifier 调用链时对绘制效果有大致的预判。
DrawModifier 负责绘制的管理,很多时候会与管理布局的 LayoutModifier 放在一起使用。比如:
Box(Modifier.requiredSize(80.dp).background(Color.Blue).requiredSize(40.dp))
思考一下结果如何,可以带着疑问进入 DrawModifier 的学习,在文章末尾我们会再回头看这个问题。
1、基本用法
DrawModifier 是 Modifier.Element 的子接口,它只有一个负责进行绘制的函数 draw():
DrawModifier 接口内只有一个函数 draw():
@JvmDefaultWithCompatibility
interface DrawModifier : Modifier.Element {
fun ContentDrawScope.draw()
}
使用上,可以通过 then() 连接一个 DrawModifier 的匿名对象:
Modifier.then(object : DrawModifier {
override fun ContentDrawScope.draw() {
// 绘制内容...
}
})
或者使用简便函数 drawWithContent():
fun Modifier.drawWithContent(
onDraw: ContentDrawScope.() -> Unit
): Modifier = this.then(
DrawWithContentModifier(
onDraw = onDraw,
inspectorInfo = debugInspectorInfo {
name = "drawWithContent"
properties["onDraw"] = onDraw
}
)
)
DrawWithContentModifier 在实现 DrawModifier 时实际上就是调用了参数上传入的 onDraw:
private class DrawWithContentModifier(
val onDraw: ContentDrawScope.() -> Unit,
inspectorInfo: InspectorInfo.() -> Unit
) : DrawModifier, InspectorValueInfo(inspectorInfo) {
// 实现 DrawModifier 接口函数,调用参数传入的 onDraw() 实现绘制
override fun ContentDrawScope.draw() {
onDraw()
}
}
因此两种使用方式其实是等价的。
在使用上述两种方式进行绘制时,需要注意,如果你想在原有绘制内容的基础上再多绘制一些内容,你需要调用 drawContent():
Modifier.drawWithContent {
// 绘制原有内容
drawContent()
// 多绘制的内容,比如画一个圆,后绘制的内容会覆盖先绘制的 drawContent()
drawCircle(Color.Red)
}
如果不调用 drawContent() 绘制原有内容,drawWithContent() 绘制的内容会覆盖原有内容导致原有内容不被绘制。原有内容是指位于 Modifier 链中 drawWithContent() 右侧的内容。比如说:
Box(Modifier.background(Color.Blue).size(40.dp).drawWithContent { })
Box(Modifier.size(40.dp).drawWithContent { }.background(Color.Blue))
Box(Modifier.size(40.dp).drawWithContent { drawContent() }.background(Color.Blue))
三种写法的结果如下:
- 第一种写法会显示一个 40dp 的蓝色 Box,因为空的 drawWithContent() 右侧没有其他修饰符了,相当于它没有覆盖任何内容
- 第二种写法不会显示任何内容,因为空的 drawWithContent() 覆盖了其右侧的 background(Color.Blue),使得 Box 没有背景色了,所以显示不出任何内容
- 第三种写法会显示一个 40dp 的蓝色 Box,因为在 drawWithContent() 内调用了 drawContent(),让其右侧的 background(Color.Blue) 的内容得以绘制
drawContent() 是 ContentDrawScope 接口的函数:
@JvmDefaultWithCompatibility interface ContentDrawScope : DrawScope { fun drawContent() }
因此,该函数的调用者类型只能是 ContentDrawScope,刚好 drawWithContent() 的 onDraw 参数与 DrawWithContentModifier 的构造函数的第一个参数 onDraw 指定的接收者类型就是 ContentDrawScope,所以可以直接在 drawWithContent() 内调用 drawContent()。
在 drawWithContent() 内调用 drawContent() 才能绘制原有内容这种用法,虽然相比于让 Compose 直接帮我们绘制原有内容要多调用一个 drawContent(),稍微麻烦一点点,但是自由度却大大增加了。我们可以根据业务需求定制绘制的内容与覆盖顺序:
Box(
Modifier
.size(40.dp)
.drawWithContent {
// 业务绘制 1 代码...
drawContent()
// 业务绘制 2 代码...
}
.background(Color.Blue)
)
先绘制的内容会被后绘制的内容覆盖,可以利用这一点定制绘制内容的覆盖关系。
此外,由于 ContentDrawScope 父接口 DrawScope 内提供了绘制函数 drawLine()、drawRect()、drawImage()、drawCircle() 等,因此在 drawWithContent() 中也可以直接使用这些函数:
Box(
Modifier
.size(40.dp)
.drawWithContent {
drawContent()
// 在蓝色 Box 上面画一个红色的圆
drawCircle(Color.Red)
}
.background(Color.Blue)
)
至于业务绘制的具体代码,需要使用 Compose 的 Canvas,它内部使用了 Android 原生的 Canvas,二者在使用上会有一些区别。比如,Compose 的 Canvas 在使用上会方便一些,但是却没提供绘制文字的函数 drawText(),只能通过 Compose 的 Canvas 拿到底层 Android 原生的 Canvas 再绘制文字。更具体的内容会在后续介绍自定义绘制时再详解。
接下来我们会介绍绘制原理,主要介绍两个部分,一是 DrawModifier 是如何在 Modifier 链中被识别与存储的,二是具体的绘制过程。
2、DrawModifier 的解析
DrawModifier 的解析过程,实际上在上一篇介绍 LayoutModifier,讲解 LayoutNode 的 modifier 属性的 set() 时已经被带到了。因此这里我们只说一下大致过程,不再全方位的详细解析了。
所有用来描述界面的 Composable 函数都会被处理为 LayoutNode,这些函数的 modifier 参数,也就是 modifier 调用链会被重新构建为 LayoutNode 的 modifier 属性。在 modifier 的 set() 中,DrawModifier 对应的 DrawEntity 会在处理下一个 LayoutModifier 之前,被添加到当前所属的 LayoutNodeWrapper 的 entities 数组对应类型链表的头部:
override var modifier: Modifier = Modifier
set(value) {
...
val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap ->
...
// DrawModifier 对应的实体 DrawEntity 被添加到数组的链表中
toWrap.entities.addBeforeLayoutModifier(toWrap, mod)
...
val wrapper = if (mod is LayoutModifier) {
// Re-use the layoutNodeWrapper if possible.
(reuseLayoutNodeWrapper(toWrap, mod)
?: ModifiedLayoutNode(toWrap, mod)).apply {
onInitialize()
updateLookaheadScope(mLookaheadScope)
}
} else {
toWrap
}
wrapper.entities.addAfterLayoutModifier(wrapper, mod)
wrapper
}
...
// 用 foldOut() 的结果更新 layoutDelegate.outerWrapper,即 outerLayoutNodeWrapper
layoutDelegate.outerWrapper = outerWrapper
...
}
addBeforeLayoutModifier() 用于添加应该在 LayoutModifier 之前添加的 Modifier,包括 DrawModifier:
fun addBeforeLayoutModifier(layoutNodeWrapper: LayoutNodeWrapper, modifier: Modifier) {
if (modifier is DrawModifier) {
// add() 会把 DrawEntity 添加到 entities 数组的第 0 个链表的头部
add(DrawEntity(layoutNodeWrapper, modifier), DrawEntityType.index)
}
if (modifier is PointerInputModifier) {
add(PointerInputEntity(layoutNodeWrapper, modifier), PointerInputEntityType.index)
}
if (modifier is SemanticsModifier) {
add(SemanticsEntity(layoutNodeWrapper, modifier), SemanticsEntityType.index)
}
if (modifier is ParentDataModifier) {
add(SimpleEntity(layoutNodeWrapper, modifier), ParentDataEntityType.index)
}
}
大致过程如上,下面我们结合一个简单的实例来说说实际应用的过程。假如有一个 Box 组件使用如下的 Modifier 链:
Box(Modifier.background(Color.Blue).padding(10.dp).background(Color.Red).padding(20.dp))
按照 modifier 的 set() 内 foldOut() 的遍历逻辑,初始状态是一个 InnerPlaceable 包装 Box 对应的 LayoutNode:
InnerPlaceable(layoutNode = Box)
然后从 Modifier 链的右侧开始向左侧遍历,第一个遇到的 padding(20.dp) 是 PaddingModifier,本质是一个 LayoutModifier,因此要生成一个 ModifiedLayoutNode 包装前面的 LayoutNodeWrapper,也就是 InnerPlaceable,同时还要把这个 LayoutModifier 也包装起来:
ModifiedLayoutNode(
wrapped = InnerPlaceable(layoutNode = Box)
modifier = PaddingModifier - padding(20.dp)
)
接下来,遍历到 background(Color.Red),连接的 Background 本质是一个 DrawModifier,它对应的 Entity 会被添加到当前处于外层的 LayoutNodeWrapper,也就是 ModifiedLayoutNode 的 entities 数据对应的链表头部:
ModifiedLayoutNode(
wrapped = InnerPlaceable(layoutNode = Box)
modifier = PaddingModifier - padding(20.dp)
entities = [DrawEntity(modifier = Background(Color.Red)),null,null,null,null,null,null]
)
继续遍历到 padding(10.dp),又是一个 LayoutModifier,所以再生成一个新的 ModifiedLayoutNode 包装前面的 ModifiedLayoutNode:
ModifiedLayoutNode(
modifier = PaddingModifier - padding(10.dp)
wrapped = ModifiedLayoutNode(
wrapped = InnerPlaceable(layoutNode = Box)
modifier = PaddingModifier - padding(20.dp)
entities = [DrawEntity(modifier = Background(Color.Red)),null,null,null,null,null,null]
)
)
最后是 background(Color.Blue):
ModifiedLayoutNode(
modifier = PaddingModifier - padding(10.dp)
wrapped = ModifiedLayoutNode(
wrapped = InnerPlaceable(layoutNode = Box)
modifier = PaddingModifier - padding(20.dp)
entities = [DrawEntity(modifier = Background(Color.Red)),null,null,null,null,null,null]
)
entities = [DrawEntity(modifier = Background(Color.Blue)),null,null,null,null,null,null]
)
这样就完成了 modifier 属性的链式构建,形成了最终的“洋葱模型”。实际上 DrawModifier 就是保存在它右侧离它最近的那个 LayoutModifier 对应的 ModifiedLayoutNode 的 entities 数组的链表中。
3、绘制过程
接下来看使用时设置的 DrawModifier 是如何绘制的,具体代码在 LayoutNode 的 draw() 中:
internal fun draw(canvas: Canvas) = outerLayoutNodeWrapper.draw(canvas)
outerLayoutNodeWrapper 实际上就是上一节举例得到的最外层的 ModifiedLayoutNode,调用它的 draw() 开始绘制流程,意味着绘制是从洋葱模型的最外层开始绘制的。
outerLayoutNodeWrapper 的类型是 LayoutNodeWrapper,它的 draw() 的具体内容如下:
fun draw(canvas: Canvas) {
val layer = layer
if (layer != null) {
layer.drawLayer(canvas)
} else {
val x = position.x.toFloat()
val y = position.y.toFloat()
canvas.translate(x, y)
drawContainedDrawModifiers(canvas)
canvas.translate(-x, -y)
}
}
draw() 的绘制会根据 layer 是否为空分为两种情况,当 layer 不为空时在 layer 上绘制,当 layer 为空时在 canvas 上绘制。
我们简单说一下 layer,它是一个独立绘制的图层,起到一个分层隔离,独立刷新的作用。它可能存在,但默认情况以及大多数时候,它是不存在的。底层实现在 Android 10(API 29)以下是用一个额外独立的 View 作为图层的绘制承载工具,从 Android 10 开始使用 RenderNode。
3.1 layer 为空的绘制过程
我们先看默认 layer 为空的情况,核心的绘制功能在 LayoutNodeWrapper 的 drawContainedDrawModifiers() 中:
private fun drawContainedDrawModifiers(canvas: Canvas) {
val head = entities.head(EntityList.DrawEntityType)
if (head == null) {
performDraw(canvas)
} else {
head.draw(canvas)
}
}
head() 用于根据传入的 entityType 从 entities 数组中取出对应类型的链表头:
@Suppress("UNCHECKED_CAST")
fun <T : LayoutNodeEntity<T, M>, M : Modifier> head(entityType: EntityType<T, M>): T? =
entities[entityType.index] as T?
这里我们传参 DrawEntityType,它的 index 是 0,因此 head 就是从 entities 数组中取出的 DrawEntity 类型的链表头。
下一步根据 head 是否为空分为两种情况:
- 如果 head 为空,说明 DrawEntity 链表为空,也就是在设置 Modifier 时没有设置过 DrawModifier,那么就用 performDraw() 绘制组件自身内容即可
- 如果 head 不为空,证明我们在设置 Modifier 时至少设置了一个 DrawModifier,那么就从 DrawEntity 链表头开始绘制
没设置 DrawModifier 的绘制
我们先看第一种情况,不设置 DrawModifier 的情况下如何绘制:
internal abstract class LayoutNodeWrapper(
internal val layoutNode: LayoutNode
) : LookaheadCapablePlaceable(), Measurable, LayoutCoordinates, OwnerScope,
(Canvas) -> Unit {
internal open val wrapped: LayoutNodeWrapper? get() = null
open fun performDraw(canvas: Canvas) {
wrapped?.draw(canvas)
}
}
performDraw() 内调用当前 LayoutNodeWrapper 内包装的 LayoutNodeWrapper,也就是 wrapped 的 draw(),实际上就是把绘制流程交给它内部包装的 LayoutNodeWrapper 了。被包装的 LayoutNodeWrapper 执行 draw() 时,又会进行同样的操作:如其 entities 数组的 DrawEntity 链表不为空则从头开始逐个绘制,否则就交由它内部包装的 LayoutNodeWrapper 进行绘制。
因此我们可以推断出大致的绘制流程,就是从洋葱模型的最外层的 LayoutNodeWrapper 开始绘制,当前 LayoutNodeWrapper 的 entities 数组的 DrawEntity 链表的内容绘制完毕,或者链表为空,就交给内层的 LayoutNodeWrapper 进行绘制,直到最内层。最内层的 LayoutNodeWrapper 类型是 InnerPlaceable,它并没有重写 wrapped 属性(只有 ModifiedLayoutNode 重写了),因此 InnerPlaceable 的 wrapped 就取默认值 null,那么它在执行 performDraw() 时就不会执行 wrapped?.draw()
,整个的绘制流程也就结束了。
还是结合一个简单实例来看:
Box(Modifier.padding(8.dp).size(40.dp))
经过 modifier 的 set() 的处理,Box 被处理后的 LayoutNode 内的 outerLayoutNodeWrapper 属性结构为:
outerLayoutNodeWrapper =
ModifiedLayoutNode1(
PaddingModifier,
ModifiedLayoutNode2(
SizeModifier,
InnerPlaceable
)
)
绘制从最外层的 ModifiedLayoutNode1 开始,由于其没有设置 DrawModifier,因此交给它包装的 ModifiedLayoutNode2 进行绘制。ModifiedLayoutNode2 也没设置 DrawModifier,因此再交给它包装的 InnerPlaceable 进行绘制。但由于 InnerPlaceable 内部是没有包装其他 LayoutNodeWrapper 的,因此绘制到这里就结束了。
看到这里,不知你是否会有疑问,以上绘制流程没有提到 Box 组件本身的绘制。实际上组件本身也是在其内部通过 DrawModifier 进行绘制的。但组件内部的绘制不属于当前流程分支的内容,我们会在后面看到。
下面我们要讲设置了 DrawModifier 的绘制,也要分两种情况分析,DrawEntity 链表头节点与其他节点的绘制原理并不完全相同,分成两小节来分析。
DrawEntity 链表头节点的绘制
回头我们再看 drawContainedDrawModifiers() 中的第二种情况,使用了 DrawModifier 的情况,会调用 DrawEntity 链表头节点的 draw():
// This is not thread safe
fun draw(canvas: Canvas) {
val size = size.toSize()
...
val drawScope = layoutNode.mDrawScope
// this 是 DrawEntiry,lambda 表达式内实际上就是调用了 DrawModifier 的 draw()
drawScope.draw(canvas, size, layoutNodeWrapper, this) {
with(drawScope) {
with(modifier) {
draw()
}
}
}
}
调用 LayoutNodeDrawScope 的 draw(),lambda 表达式内实际上就是在调用 DrawModifier 的 draw() 进行绘制,这里还没被调用,只是封装到 lambda 表达式中作为函数参数 block 向下传递:
internal inline fun draw(
canvas: Canvas,
size: Size,
layoutNodeWrapper: LayoutNodeWrapper,
drawEntity: DrawEntity,
block: DrawScope.() -> Unit
) {
// 临时使用参数传进来的 drawEntity 保存到 drawEntity 属性中
val previousDrawEntity = this.drawEntity
this.drawEntity = drawEntity
canvasDrawScope.draw(
layoutNodeWrapper.measureScope,
layoutNodeWrapper.measureScope.layoutDirection,
canvas,
size,
block
)
// 绘制完成后恢复 drawEntity 属性为原来的值
this.drawEntity = previousDrawEntity
}
调用 CanvasDrawScope 的 draw(),执行了参数上的 block 函数:
inline fun draw(
density: Density,
layoutDirection: LayoutDirection,
canvas: Canvas,
size: Size,
block: DrawScope.() -> Unit
) {
// Remember the previous drawing parameters in case we are temporarily re-directing our
// drawing to a separate Layer/RenderNode only to draw that content back into the original
// Canvas. If there is no previous canvas that was being drawing into, this ends up
// resetting these parameters back to defaults defensively
val (prevDensity, prevLayoutDirection, prevCanvas, prevSize) = drawParams
drawParams.apply {
this.density = density
this.layoutDirection = layoutDirection
this.canvas = canvas
this.size = size
}
canvas.save()
// 调用了参数上的 block
this.block()
canvas.restore()
drawParams.apply {
this.density = prevDensity
this.layoutDirection = prevLayoutDirection
this.canvas = prevCanvas
this.size = prevSize
}
}
在这里最终执行了 DrawModifier 的 draw() 进行绘制。对于我们我们前面在基本用法中提到的 drawWithContent() 而言:
Box(
Modifier
.size(40.dp)
.drawWithContent {
drawContent()
// 在蓝色 Box 上面画一个红色的圆
drawCircle(Color.Red)
}
.background(Color.Blue)
)
drawWithContent() 后的 lambda 表达式是它的 onDraw 参数:
fun Modifier.drawWithContent(
onDraw: ContentDrawScope.() -> Unit
): Modifier = this.then(
DrawWithContentModifier(
onDraw = onDraw,
inspectorInfo = debugInspectorInfo {
name = "drawWithContent"
properties["onDraw"] = onDraw
}
)
)
这个 onDraw 是在 DrawWithContentModifier 实现 DrawModifier 接口的 ContentDrawScope.draw() 时被调用的:
private class DrawWithContentModifier(
val onDraw: ContentDrawScope.() -> Unit,
inspectorInfo: InspectorInfo.() -> Unit
) : DrawModifier, InspectorValueInfo(inspectorInfo) {
override fun ContentDrawScope.draw() {
onDraw()
}
}
因此,绘制的内容就是 drawWithContent() 后的 lambda 表达式内的内容。
DrawEntity 链表其他节点的绘制
上面只讲了头节点 head 的绘制,但还要考虑 Modifier 链上其他的 DrawModifier 是如何绘制的。
我们讲用法时说过,需要在 drawWithContent() 内调用的 drawContent() 才能绘制原有内容,否则就只会绘制当前这个 drawWithContent() 内所指定的绘制内容。这是因为,DrawEntity 链表其他节点的绘制正是通过 drawContent() 实现的。
drawContent() 是 ContentDrawScope 接口的函数,实现类只有一个 LayoutNodeDrawScope:
internal class LayoutNodeDrawScope(
private val canvasDrawScope: CanvasDrawScope = CanvasDrawScope()
) : DrawScope by canvasDrawScope, ContentDrawScope {
private var drawEntity: DrawEntity? = null
override fun drawContent() {
drawIntoCanvas { canvas ->
val drawEntity = drawEntity!!
val nextDrawEntity = drawEntity.next
// 如 DrawEntity 链表的下一个节点不为空则让其绘制
if (nextDrawEntity != null) {
nextDrawEntity.draw(canvas)
} else {
// 下一个节点为空说明链表尾节点已经绘制完成,需要交给链表所属的 LayoutNodeWrapper
// 进行绘制,实际上就是交给被包装的 LayoutNodeWrapper 进行绘制
drawEntity.layoutNodeWrapper.performDraw(canvas)
}
}
}
}
drawIntoCanvas() 只是接收了一个 Canvas 参数然后直接调用了它的 lambda 表达式参数 block:
inline fun DrawScope.drawIntoCanvas(block: (Canvas) -> Unit) = block(drawContext.canvas)
block 内会取 drawEntity 的下一个节点 nextDrawEntity,如果为空,就调用 drawEntity 所在的 LayoutNodeWrapper 的 performDraw(),这个我们前面已经看过了:
open fun performDraw(canvas: Canvas) {
// 让当前 LayoutNodeWrapper 内包装的 LayoutNodeWrapper 开始绘制,
// 如果到了最内层的 InnerPlaceable,wrapped 为 null 就不绘制
wrapped?.draw(canvas)
}
如果 nextDrawEntity 不为空,则执行 nextDrawEntity 的 draw():
// This is not thread safe
fun draw(canvas: Canvas) {
val size = size.toSize()
if (cacheDrawModifier != null && invalidateCache) {
layoutNode.requireOwner().snapshotObserver.observeReads(
this,
onCommitAffectingDrawEntity,
updateCache
)
}
val drawScope = layoutNode.mDrawScope
drawScope.draw(canvas, size, layoutNodeWrapper, this) {
with(drawScope) {
with(modifier) {
draw()
}
}
}
}
这个代码过程在上一小节讲 DrawEntity 链表头节点的绘制时已经说过,最终会根据 DrawModifier 指定的绘制内容去进行绘制。
所以,想要绘制 Modifier 链中 DrawEntity 链表的非头节点,你必须调用 drawContent(),它会取 DrawEntity 链表的下一个节点,如果节点非空则让该节点进行绘制,否则就交给该节点所在的 LayoutNodeWrapper,让它触发它内部包装的 LayoutNodeWrapper 的绘制流程。
本小节的最后,我们也再次强调一下,为了让绘制链条不中断,一定记得要在定义 DrawModifier 时调用 drawContent()。
总结
以上基本上就是完整的绘制流程了,因为 layer 不为空的情况,启动绘制调用的还是 drawContainedDrawModifiers() 这个函数,只不过就是启动绘制之前的代码路径与 layer 为空的不同罢了。因此我们将以上源码原理结合一个简单的代码示例做一个小总结。
假如有如下的 Box 组件:
Box(
Modifier.then(DrawModifier1).then(DrawModifier2).padding(8.dp)
.then(DrawModifier3).size(40.dp)
.then(DrawModifier4).then(DrawModifier5)
)
对 Box 处理过后形成的 LayoutNode,其 modifier 属性的 set() 会对 Modifier 调用链进行解析与重新构建,形成的 outerLayoutNodeWrapper 的结构如下:
ModifiedLayoutNode1(
entities = [DrawModifier1 -> DrawModifier2,null,null,null,null,null,null]
modifier = PaddingModifier,
wrapped = ModifiedLayoutNode2(
entities = [DrawModifier3,null,null,null,null,null,null]
modifier = SizeModifier,
wrapped = InnerPlaceable(
entities = [DrawModifier4 -> DrawModifier5,null,null,null,null,null,null]
)
)
)
简单解释一下这个结构的形成过程:
- 从 Modifier 调用链的右侧开始遍历,初始值是 InnerPlaceable,遇到 then(DrawModifier5) 将 DrawModifier5 对应的 DrawEntity 存到当前最外层的 LayoutNodeWrapper,也就是 InnerPlaceable 的 entities 数组第 0 个位置的链表上
- 接下来遍历到 then(DrawModifier4),还是要将包装了 DrawModifier4 的 DrawEntity 添加到 InnerPlaceable 的 entities 中,由于链表采用头插法,因此 entities 的 DrawEntity 链表就是 DrawModifier4 -> DrawModifier5
- 继续遍历到 size(40.dp),SizeModifier 是一个 LayoutModifier,因此要创建一个 ModifiedLayoutNode 包装目前最外层的 LayoutNodeWrapper —— InnerPlaceable 以及相应的 LayoutModifier —— SizeModifier
- 再遍历,遇到 then(DrawModifier3),需要将包装了 DrawModifier3 的 DrawEntity 添加到最外层 LayoutNodeWrapper —— ModifiedLayoutNode2 的 entities 中
- 后续步骤与前面类似,就不再赘述了
然后我们再看绘制流程:
- LayoutNode 的 draw() 调用 outerLayoutNodeWrapper,也就是 ModifiedLayoutNode1 的 draw() 开始绘制
- ModifiedLayoutNode1 看自己的 entities 的 DrawEntity 链表,调用表头,也就是 DrawModifier1 的 draw() 绘制表头中的内容
- 表头绘制时需调用 drawContent(),它会执行下一个节点 DrawModifier2 的 draw()
- DrawModifier2 的 draw() 内也需调用 drawContent(),这样才会在没有下一个节点的情况下,让 ModifiedLayoutNode1 调用它的 wrapped 属性,也就是 ModifiedLayoutNode2 的 draw()
- ModifiedLayoutNode2 也是一个 LayoutNodeWrapper,调用它的 draw() 的过程就是第一步到上一步的过程,如出一辙,就是 ModifiedLayoutNode2 找它的 DrawEntity 链表进行绘制,链表尾的 DrawEntity 通过内部调用的 drawContent() 找到 ModifiedLayoutNode2 包含的下一个 LayoutNodeWrapper —— InnerPlaceable
- InnerPlaceable 还是同样的找 DrawEntity 链表,按照顺序绘制 DrawModifier4、DrawModifier5、DrawModifier6,在 DrawModifier6 的 drawContent() 内,由于没有后续节点了,就会执行 InnerPlaceable 的 performDraw(),由于 InnerPlaceable 的 wrapped 属性为 null,这样 performDraw() 内的 wrapped?.draw() 就不会执行,绘制结束
因此我们能看到,绘制是按照 Modifier 链上的 DrawModifier 从左到右的顺序绘制的。
将上述过程形成伪代码结构,假如每个 DrawModifier 内都是先绘制自己的内容后再调用 drawContent(),就是如下的包裹关系:
DrawModifier1.draw() {
// 先绘制 DrawModifier1 自身内容...
drawContent() {
DrawModifier2.draw() {
// 先绘制 DrawModifier2 自身内容...
drawContent() {
DrawModifier3.draw() {
...
}
}
}
}
}
但假如每个 DrawModifier 都是先调用 drawContent() 再绘制自己的内容,包裹关系就变成:
DrawModifier1.draw() {
drawContent() {
DrawModifier2.draw() {
drawContent() {
DrawModifier3.draw() {
...
}
}
// 后绘制 DrawModifier2 自身内容...
}
}
// 后绘制 DrawModifier1 自身内容...
}
3.2 layer 不为空的绘制过程
layer 这个知识有点偏,因此我们就只简单说说。
回到 LayoutNodeWrapper 的 draw() 中,当 layer 不为空时,调用 layer 的 drawLayer():
fun draw(canvas: Canvas) {
val layer = layer
if (layer != null) {
layer.drawLayer(canvas)
} else {
val x = position.x.toFloat()
val y = position.y.toFloat()
canvas.translate(x, y)
drawContainedDrawModifiers(canvas)
canvas.translate(-x, -y)
}
}
这个 layer 是 OwnedLayer 接口的实例,该接口有两个实现类 ViewLayer 与 RenderNodeLayer,我们只看后者的实现:
override fun drawLayer(canvas: Canvas) {
val androidCanvas = canvas.nativeCanvas
if (androidCanvas.isHardwareAccelerated) {
...
} else {
...
drawBlock?.invoke(canvas)
...
}
}
关键的执行绘制的语句就是调用 drawBlock 函数,它是 RenderNodeLayer 内的属性:
private var drawBlock: ((Canvas) -> Unit)? = drawBlock
这个函数的赋值是在 onLayerBlockUpdated() 中,调用 createLayer() 创建 layer 时:
fun onLayerBlockUpdated(layerBlock: (GraphicsLayerScope.() -> Unit)?) {
val layerInvalidated = this.layerBlock !== layerBlock || layerDensity != layoutNode
.density || layerLayoutDirection != layoutNode.layoutDirection
this.layerBlock = layerBlock
this.layerDensity = layoutNode.density
this.layerLayoutDirection = layoutNode.layoutDirection
if (isAttached && layerBlock != null) {
if (layer == null) {
layer = layoutNode.requireOwner().createLayer(
this,
invalidateParentLayer
).apply {
resize(measuredSize)
move(position)
}
updateLayerParameters()
layoutNode.innerLayerWrapperIsDirty = true
invalidateParentLayer()
} else if (layerInvalidated) {
updateLayerParameters()
}
} else {
...
}
}
createLayer() 是 Owner 接口的函数,实现是在 AndroidComposeView 中,第一个参数就是 drawBlock:
override fun createLayer(
drawBlock: (Canvas) -> Unit,
invalidateParentLayer: () -> Unit
): OwnedLayer {
...
}
而 onLayerBlockUpdated() 中给这个 drawBlock 传的是 this,实际就指向了 invoke():
@Suppress("LiftReturnOrAssignment")
override fun invoke(canvas: Canvas) {
if (layoutNode.isPlaced) {
snapshotObserver.observeReads(this, onCommitAffectingLayer) {
drawContainedDrawModifiers(canvas)
}
lastLayerDrawingWasSkipped = false
} else {
lastLayerDrawingWasSkipped = true
}
}
invoke() 内会调用 drawContainedDrawModifiers():
private fun drawContainedDrawModifiers(canvas: Canvas) {
val head = entities.head(EntityList.DrawEntityType)
if (head == null) {
performDraw(canvas)
} else {
head.draw(canvas)
}
}
这个函数你应该很熟悉了,前面讲 layer 为空的绘制流程时,需要让 LayoutNodeWrapper 进行绘制时,就是调用的这个函数。
因此,layer 不为空实际上也是通过 LayoutNodeWrapper 的 drawContainedDrawModifiers() 进行的绘制。
4、举例与总结
为了更好地将源码的原理与实际应用融合贯通,我们举一些例子再梳理一下绘制流程。
首先,判断如下 Box 的背景颜色:
Box(Modifier.size(40.dp).background(Color.Red).background(Color.Blue))
答案是蓝色。LayoutNode 的 outerLayoutNodeWrapper 如下:
ModifiedLayoutNode[
SizeModifier,
InnerPlaceable(entities = [Background(Red) -> Background(Blue),null,null,null,null,null,null])
]
在绘制时,按照链表从头到尾的顺序绘制,因此先绘制 Red,再绘制 Blue,后绘制的会覆盖前面的,因此 Box 的颜色是蓝色。也就是说,相同的绘制属性,靠右侧的 Modifier 生效。
再看第二个例子,这是本篇文章开篇提到的问题,判断如下 Box 的蓝色区域的尺寸是多少:
Box(Modifier.requiredSize(80.dp).background(Color.Blue).requiredSize(40.dp))
答案是 40,它形成的是一个 80 的 Box,但是居中的蓝色方块是 40:

outerLayoutNodeWrapper 的结果为:
ModifiedLayoutNode(
entities = [null,null,null,null,null,null,null]
modifier = SizeModifier(80.dp)
wrapped = ModifiedLayoutNode(
entities = [Background,null,null,null,null,null,null]
modifier = SizeModifier(40.dp),
wrapped = InnerPlaceable(entities = [null,null,null,null,null,null,null])
)
)
内层的 ModifiedLayoutNode 尺寸为 40dp,蓝色背景,而外层的 ModifiedLayoutNode 尺寸 80dp,没有背景,与结果吻合。再次印证 DrawModifier 会被添加到它右侧最近的那个 LayoutModifier 表示的 ModifiedLayoutNode 中。比如在现有基础上再加一个 background():
Box(Modifier.background(Color.Red).requiredSize(80.dp).background(Color.Blue).requiredSize(40.dp))
红色的 Background 会被添加到 80 的 ModifiedLayoutNode 中:

最后一个例子,下面 Box 的蓝色背景的尺寸是多大,如何计算的:
Box(Modifier.size(40.dp).padding(8.dp).background(Color.Blue))
答案是 24,计算方式是,从右向左,先让 InnerPlaceable 的背景是蓝色的,然后让 padding 是 8dp,最后是 Box 的尺寸 40dp,这样让 InnerPlaceable 测量出的尺寸为 24,四周有 8dp 的内边距。这个结果会给人造成一种,尺寸是从左向右计算得出的错觉,假如你把这种错觉应用到两个方向不同结果的情况时,就会得出错误的结果,比如:
Box(Modifier.size(40.dp).padding(8.dp).background(Color.Blue).requiredSize(34.dp))
如果按照正确的从右向左算,会得到一个 40dp 的 Box,居中摆着 34dp 的蓝色方块,四个方向内边距为 3dp;而如果按照错觉的从左向右计算,会得到一个 34dp 的蓝色 Box。
总结:
- 从绘制关系上看,DrawModifier 是从左到右的包裹关系(数据结构关系是链表,最左侧的 DrawModifier 是链表头),需要通过手动 drawContent() 才能确保所有 DrawModifier 都得以绘制,否则绘制链条会断掉
- DrawModifier 绘制出的尺寸,由距他最近的 LayoutModifier 决定