深度解析 Compose 的 Modifier 原理 -- DrawModifier

发布时间:2024年01月09日

其实原理性分析的文章,真的很难讲的通俗易懂,讲的简单了就没必要写了,讲的繁琐难懂往往大家也不乐意看,所以只能尽量想办法,找个好的角度(比如从 Demo 代码示例出发)慢慢带着大家去钻源码,如果确实能帮助到大家完全理解了文章所讲述到的源码理论,那就值了。

在正式开始分析 DrawModifier 之前,建议你先看看 【LayoutModifier 和 Modifier.layout 用法及原理】这篇文章,毕竟它是作为 Modifier 原理解析的第一篇文章,对你了解整个 Modifier 架构还是很有帮助的,或者说它是最基础的一篇文章,如果不熟悉,后面的系列 Modifier 你可能会看的比较费劲… …


Modifier 系列文章

??📑 《 深入解析 Compose 的 Modifier 原理 - - Modifier、CombinedModifier 》

??📑 《 深度解析 Compose 的 Modifier 原理 - - Modifier.composed()、ComposedModifier 》

??📑 《 深入解析 Compose 的 Modifier 原理 - - LayoutModifier 和 Modifier.layout 》


一、话题引入


老样子,我们从 Demo 开始,首先我们看两个简单的场景:

1.1 问题场景 1

一个绿色背景的 Text():

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green)) {
                    Text("Hi Compose")
                }
            }
        }
    }
}

效果如下:

在这里插入图片描述


如果我改动一下代码:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green).size(120.dp)) {
                    Text("Hi Compose")
                }
            }
        }
    }
}

只是给 Box 加了一个尺寸:

在这里插入图片描述

嗯,效果不错,那如果我再改一次呢?

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green).size(120.dp).background(Color.Red)) {
                    Text("Hi Compose")
                }
            }
        }
    }
}

又加了一个 background,看下效果:

在这里插入图片描述

思考一个问题:为什么此时的 Box 是红色,而不是绿色? 如果你看了 LayoutModifier 的原理解析,这边就会产生一个困惑:Modifier 不是从右边开始遍历,以此往左,最终应该是绿色背景,而不应该是红色背景啊?

所以,问题出在哪?我们不妨先看下 Modifier.background() 源码:

fun Modifier.background(
    color: Color,
    shape: Shape = RectangleShape
) = this.then(
    Background(
        ... ...
    )
)

它又调用了 Background

private class Background constructor(
    ... ...
) : DrawModifier, InspectorValueInfo(inspectorInfo) {

这里可以发现 Background 又是 DrawModifier 的实现类,所以我们可以给出一个猜测:难道是因为 DrawModifier 影响了方块的 Backgroud?

==> 答案是肯定的!

1.2 问题场景 2

在之前我们分析 LayoutModifier 文章中提到过:如果我们需要自己创建一个 LayoutModifier,可以通过 Modifier.layout(),同样的如果我们要创建一个 DrawModifier,可以通过 Modifier.drawWithContent(),比如:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green).size(120.dp).drawWithContent {  }) {
                    Text("Hi Compose")
                }
            }
        }
    }
}

看下效果:

在这里插入图片描述

???不对啊,Text() 哪去了?我现在再来修改下代码:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green).size(120.dp).drawWithContent { drawContent() }) {
                    Text("Hi Compose")
                }
            }
        }
    }
}

仅仅是在 drawWithContent 里面加了一个 drawContent(),再看下效果:

在这里插入图片描述

???Text() 又出来了,为什么?为什么要加 drawContent(), drawContent() 又是什么?难道它负责绘制 Text()?

==> 答案是肯定的!

所以接下来,我们就带着这两个问题正式进入这篇文章的主题!


二、DrawModifier


就像我们前面在分析 LayoutModifier 的时候说过,所有组件最终都会被转换为一个 LayoutNode,这个LayoutNode 包含了所有的测量数据,那同样它也会包含你对组件设定的 Modifier,所以最终经过一些列转换,也会传到 LayoutNode 里面,那 LayoutNode 里面必然会存在一个 modifer 属性来处理你所设定的所有 Modifier,我们之前看过 LayoutModifer 的处理逻辑了,那么如果是一个 DrawModifier 呢?

2.1 LayoutNode.modifier

正如前面所说,LayoutNode 中会对 Modifier 做处理:

override var modifier: Modifier = Modifier
    set(value) {
        ... ...
        
        val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap ->
            if (mod is RemeasurementModifier) {
                mod.onRemeasurementAvailable(this)
            }

            // 处理 DrawModifier 的逻辑
            toWrap.entities.addBeforeLayoutModifier(toWrap, mod)

            if (mod is OnGloballyPositionedModifier) {
                getOrCreateOnPositionedCallbacks() += toWrap to mod
            }

            // 处理 LayoutModifier 的逻辑
            val wrapper = if (mod is LayoutModifier) {
                // Re-use the layoutNodeWrapper if possible.
                (reuseLayoutNodeWrapper(toWrap, mod)
                    ?: ModifiedLayoutNode(toWrap, mod)).apply { onInitialize() }
            } else {
                toWrap
            }
            wrapper.entities.addAfterLayoutModifier(wrapper, mod)
            wrapper
        }
        ... ...
    }

处理 DrawModifier 的核心代码就在:

toWrap.entities.addBeforeLayoutModifier(toWrap, mod)

现在就存在两个问题:

  1. topWrap 是什么?
// 比如,这是我们最开始时候例子里面的 Modifier
Modifier.background(Color.Green).size(120.dp)

当 foldOut 开始遍历的时候:

val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap ->

最初的 toWrap 是:innerLayoutNodeWrapper,也就是 InnerPlaceable。

internal val innerLayoutNodeWrapper: LayoutNodeWrapper = InnerPlaceable(this)

遵循从右往左遍历的规则,首先是处理 .size(120).dp,它是 LayoutModifier,所以会被 ModifiedLayoutNode 包起来:

ModifiedLayoutNode[
	LayoutModifier         // size(120.dp)
	+
	innerLayoutNodeWrapper // Box()
]

处理完后开始处理 background(Color.Green)

val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap ->
	... ...
	toWrap.entities.addBeforeLayoutModifier(toWrap, mod)
	...
}

这个时候的 toWrap 就是嵌套之后的 ModifiedLayoutNode,也就是一个 LayoutNodeWrapper对象,mod 就是 DrawModifier。

  1. entities 是什么?
/**
 * All [LayoutNodeEntity] elements that are associated with this [LayoutNodeWrapper].
 */
val entities = EntityList()

看它的注释:与此 [LayoutNodeWrapper] 关联的所有 [LayoutNodeEntity] 元素。

LayoutNodeEntity 又是什么?

/**
 * Base class for entities in [LayoutNodeWrapper]. a [LayoutNodeEntity] is
 * a node in a linked list referenced from [EntityList].
 */
internal open class LayoutNodeEntity<T : LayoutNodeEntity<T, M>, M : Modifier>(
    val layoutNodeWrapper: LayoutNodeWrapper,
    val modifier: M
)

我们也来解读一下注释,也就是两层意思:

1. [LayoutNodeWrapper] 中 entites 的基类。 
2. [LayoutNodeEntity] 是从 [EntityList] 引用的链表中的节点。

通过这个注释我们就可以得到两个核心信息:

1. [LayoutNodeWrapper] 中 entites 的基类。 
    ==> 基类?那就是说可能有很多这个基类的子类?

2. [LayoutNodeEntity] 是从 [EntityList] 引用的链表中的节点。
    ==> 会有一个包含链表的 EntityList?这个基类的子类都是链表的节点?

2.2 EntityList.addBeforeLayoutModifier()

带着这两个猜测,我们现在来重点看下 addBeforeLayoutModifier() 方法:

internal value class EntityList(
    val entities: Array<LayoutNodeEntity<*, *>?> = arrayOfNulls(TypeCount)
) {
    fun addBeforeLayoutModifier(layoutNodeWrapper: LayoutNodeWrapper, modifier: Modifier) {
        if (modifier is DrawModifier) {
            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)
        }
    }
    ... ...
}

看到没,在 addBeforeLayoutModifier 里面做了 modifier is DrawModifier 判断。

if (modifier is DrawModifier) {
    add(DrawEntity(layoutNodeWrapper, modifier), DrawEntityType.index)
}

这里我们兵分三路:

  1. DrawEntity 是什么?
internal class DrawEntity(
    layoutNodeWrapper: LayoutNodeWrapper,
    modifier: DrawModifier
) : LayoutNodeEntity<DrawEntity, DrawModifier>(layoutNodeWrapper, modifier), OwnerScope {
	... ...
}

咦?它原来是 LayoutNodeEntity 的子类!是不是解答了我们刚才的第一个猜想?- - 传进来的 DrawModifier,会被封装进一个 DrawEntity 对象里面,大概是这么个意思:

Modifier.background(Color.Green).size(120.dp)

DrawEntity[
	DrawModifier               // background(Color.Green)
	+ 
	ModifiedLayoutNode[
		LayoutModifier         // size(120.dp)
		+
		innerLayoutNodeWrapper // Box()
	]
]
  1. DrawEntityType.index 是啥?
value class EntityType<T : LayoutNodeEntity<T, M>, M : Modifier>(val index: Int)

companion object {
    val DrawEntityType = EntityType<DrawEntity, DrawModifier>(0)
    ... ...
}

原来就是一个取值的工作,返回一个固定值:0,这个值有什么用?我们先往下看。

  1. add 做了什么?

现在我们来看看 add 把 DrawEntity 给加到哪里去了:

private fun <T : LayoutNodeEntity<T, *>> add(entity: T, index: Int) {
    @Suppress("UNCHECKED_CAST")
    val head = entities[index] as T?
    entity.next = head
    entities[index] = entity
}

这是一个典型的数组 + 链表操作。

我们先看下 entities 是个啥?

internal value class EntityList(
    val entities: Array<LayoutNodeEntity<*, *>?> = arrayOfNulls(TypeCount)
) {
    fun addBeforeLayoutModifier(layoutNodeWrapper: LayoutNodeWrapper, modifier: Modifier) {
        if (modifier is DrawModifier) {
            add(DrawEntity(layoutNodeWrapper, modifier), DrawEntityType.index)
        }
        ... ...
    }
    ... ...
}

好熟悉的代码…,原来 entities 是一个数组,而且是一个长度为 7 的数组,并且它的元素都是 LayoutNodeEntity 对象。

private const val TypeCount = 7

还记得我们刚刚说的 DrawEntityType.index 的返回值:0 吗?它被传了进来,意思就是 entities 数组的 0 号为就是专门用来放 DrawModifier的。同时 entities 这个数组里面的每一个元素是一个链表。

比如有 1 个 DrawModifier,大概是这么个样子存放:

[
	LayoutNodeEntity(DrawModifier1),
	null,
	null,
	null,
	null,
	null,
	null,
]

比如有 2 个 DrawModifier,大概是这么个样子存放:

[
	LayoutNodeEntity(DrawModifier2) -> LayoutNodeEntity(DrawModifier1),
	null,
	null,
	null,
	null,
	null,
	null,
]

以此类推,后进来的就插入表头,一个链接一个。


三、 DrawModifier 对绘制的影响


现在我们已经很清楚 LayoutNode 是如何处理 DrawModifier 的了,接下来我们就看看 DrawModifier 是如何对绘制产生精细影响的。

在 LayoutNode 中,绘制是由 draw() 函数处理的。

internal fun draw(canvas: Canvas) = outerLayoutNodeWrapper.draw(canvas)

这里的 outerLayoutNodeWrapper 是什么?

3.1 draw()

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)
        drawContainedDrawModifiers(canvas)
        canvas.translate(-x, -y)
    }
}

大多数情况下,我们并不需要考虑 layer 图层,所以这里可以当它为 null 处理。所以我们只需要关心 else 分支内部的逻辑,核心代码就一行:drawContainedDrawModifiers(canvas)

2.6 drawContainedDrawModifiers()

private fun drawContainedDrawModifiers(canvas: Canvas) {
    val head = entities.head(EntityList.DrawEntityType)
    if (head == null) {
        performDraw(canvas)
    } else {
        head.draw(canvas)
    }
}

这段代码的逻辑很清晰,主要就是三步:

  1. 取 DrawEntity 链表表头
val head = entities.head(EntityList.DrawEntityType)
  1. 表头如果为空,也就是没有设置过 DrawModifier
if (head == null) {
    performDraw(canvas)
}

那么就会执行 performDraw(),我们来看看它做了什么:

open fun performDraw(canvas: Canvas) {
    wrapped?.draw(canvas)
}

wrapped 是什么?

internal open val wrapped: LayoutNodeWrapper? get() = null

它就是当前的 LayoutNodeWrapper 对象,而 LayoutNodeWrapper 是一个抽象类。

注意: 我们现在分析的场景是没有设置 DrawModifier,那么实际代码可能是这样:

Box(Modifier.padding(10.dp).size(120.dp))

对应的内部 Modifier 的结构大概是这个样子:

ModifiedLayoutNode[
	LayoutModifier             // padding(10.dp)
	+
	ModifiedLayoutNode[
		LayoutModifier         // size(120.dp)
		+
		innerLayoutNodeWrapper // Box()
	]
]

ModifiedLayoutNode 本身就是一个 LayoutNodeWrapper 的子类,所以,你现在知道上面的 wrapped 是什么了吗?

再来看看 wrapped.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() 方法了,而你还记得 drawContainedDrawModifiers 是干嘛的吗?它的核心就是用来检查是否有 DrawModifier 的链表的表头,如果没有就一直往内部找:

ModifiedLayoutNode[
	LayoutModifier             // padding(10.dp) --> 没有设置 DrawModifier,就去找内部有没有设置 DrawModifier
	+
	ModifiedLayoutNode[
		LayoutModifier         // size(120.dp) --> 没有设置 DrawModifier,就去找内部有没有设置 DrawModifier
		+
		innerLayoutNodeWrapper // Box() --> 没有设置 DrawModifier,它没有内部了
	]
]

所以我们总结下:不管你设置了多少 Modifier.**,在绘制的时候,都会遍历一遍,只要没有 DrawModifier 就不会进行任何绘制动作。

  1. 表头不为空,有设置 DrawModifier
else {
    head.draw(canvas)
}

现在我们设置了 DrawModifier,那么就会执行 head.draw(),我们再来看看它又做了什么:

// DrawEntity.kt
fun draw(canvas: Canvas) {
    ... ...

    val drawScope = layoutNode.mDrawScope
    // 1. 核心代码
    drawScope.draw(canvas, size, layoutNodeWrapper, this) {
        with(drawScope) {
            with(modifier) {
                draw()
            }
        }
    }
}

// LayoutNodeDrawScope.kt
internal inline fun draw(
    canvas: Canvas,
    size: Size,
    layoutNodeWrapper: LayoutNodeWrapper,
    drawEntity: DrawEntity,
    block: DrawScope.() -> Unit
) {
    val previousDrawEntity = this.drawEntity
    this.drawEntity = drawEntity
    // 2. 核心代码
    canvasDrawScope.draw(
        layoutNodeWrapper.measureScope,
        layoutNodeWrapper.measureScope.layoutDirection,
        canvas,
        size,
        block
    )
    this.drawEntity = previousDrawEntity
}

// CanvasDrawScope.kt
inline fun draw(
    density: Density,
    layoutDirection: LayoutDirection,
    canvas: Canvas,
    size: Size,
    block: DrawScope.() -> Unit
) {
    ... ...
    this.block() // 3. 核心代码
    ... ...
}

最终调用了 block(),这个 block 太熟了,它肯定是一个传进来的 Lambda 表达式,也就是:

在这里插入图片描述

所以我们的重点就转移到研究这段 lambda 表达式的工作内容了。

它里面也有一个 draw(),我们看看它做了啥:

// DrawModifier.kt
@JvmDefaultWithCompatibility
interface DrawModifier : Modifier.Element {

    fun ContentDrawScope.draw()
}

咦?是 DrawModifier 接口的 draw() 方法,那肯定有某个这个接口的实现类实现了这个 draw() 方法,我们可以搜索下看看:

在这里插入图片描述

看到没,Background

所以我们现在可以来看下 Background 的内部的 draw() 逻辑:

private class Background constructor(
    ... ...
) : DrawModifier, InspectorValueInfo(inspectorInfo) {
    ... ...

    override fun ContentDrawScope.draw() {
        if (shape === RectangleShape) {
            // shortcut to avoid Outline calculation and allocation
            drawRect()
        } else {
            drawOutline()
        }
        drawContent()
    }
    ... ...
}

咦?出现了一个 drawContent()

还记得我们文章开头第二个疑惑的问题吗?我们回顾一下代码:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            ComposeBlogTheme {
                Box(Modifier.background(Color.Green).size(120.dp).drawWithContent { drawContent() }) {
                    Text("Hi Compose")
                }
            }
        }
    }
}

现在我们就可以来看看 drawWithContent 的内部代码:

// DrawModifier.kt
fun Modifier.drawWithContent(
    onDraw: ContentDrawScope.() -> Unit
): Modifier = this then DrawWithContentElement(onDraw)

@OptIn(ExperimentalComposeUiApi::class)
private data class DrawWithContentElement(
    val onDraw: ContentDrawScope.() -> Unit
) : ModifierNodeElement<DrawWithContentModifier>() {
    override fun create() = DrawWithContentModifier(onDraw) // 创建 DrawWithContentModifier
	... ...
}

@OptIn(ExperimentalComposeUiApi::class)
private class DrawWithContentModifier(
    var onDraw: ContentDrawScope.() -> Unit
) : Modifier.Node(), DrawModifierNode {

    override fun ContentDrawScope.draw() {
        onDraw()  // 这不就类似于 block?
    }
}

所以实际上调用的是啥?

在这里插入图片描述

也是调用了 drawContent()。

现在我们就可以重点研究下 drawContent() 到底是做了什么了!

interface ContentDrawScope : DrawScope {
    /**
     * Causes child drawing operations to run during the `onPaint` lambda.
     */
    fun drawContent()
}

又是一个接口内部的方法,再搜一下哪个地方实现了 - - 全局只有一个地方实现了,如下:

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
            if (nextDrawEntity != null) {
                nextDrawEntity.draw(canvas)
            } else {
                drawEntity.layoutNodeWrapper.performDraw(canvas)
            }
        }
    }
	... ...
}

drawIntoCanvas 是什么?

inline fun DrawScope.drawIntoCanvas(block: (Canvas) -> Unit) = block(drawContext.canvas)

又是一个 block,所以我们只关心 lambda 表达式内部的逻辑即可,分两步走:

  1. 如果 DrawModifier 链表的下一个节点不为 null,说明还有 DrawModifier,从 DrawModifier 链表头开始遍历调用 DrawModifier 处理绘制。
  2. 如果 DrawModifier 链表的下一个节点为 null,说明只有一个 DrawModifier 链表头或没有更多 DrawModifier,就让你包裹的子节点的 DrawModifier 链表上去绘制。
文章来源:https://blog.csdn.net/pepsimaxin/article/details/135039640
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。