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

news2024/11/15 11:53:37

在这里插入图片描述


" Jetpack Compose - - Modifier 系列文章 "


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

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

    📑 《 深入解析 Compose 的 Modifier 原理 - - Modifier.layout()、LayoutModifier 》

    📑 《 深度解析 Compose 的 Modifier 原理 - - DrawModifier 》

    📑 《 深度解析 Compose 的 Modifier 原理 - - PointerInputModifier 》

    📑 《 深度解析 Compose 的 Modifier 原理 - - ParentDataModifier 》


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

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


一、话题引入


老样子,我们从 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。(不懂的话,那就抓紧去看 LayoutModifier 文章)

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()

// LayoutNodeWrapper.kt

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 是什么了吗?

ModifiedLayoutNode[            // wrapped
	LayoutModifier
	+
	ModifiedLayoutNode[        // wrapped
		LayoutModifier
		+
		innerLayoutNodeWrapper // 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:比如 Modifier.background(),那么就会执行 head.draw(),我们再来看看它又做了什么:

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

    val drawScope = layoutNode.mDrawScope
    // 1. 核心代码,看看 drawScope.draw() 做了什么工作
    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)

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

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

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

所以实际上调用的是啥?

在这里插入图片描述

也就是调用了 drawContent()。

所以不管是 Modifier.background() 还是你自己 drawWithContent(),都会/需要调用 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,这个时候就会让下一个 LayoutNodeWrapper 查找其内部有没有 DrawModifier。

比如:

Box(Modifier.padding(10.dp).background(Color.Red).size(120.dp))

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

ModifiedLayoutNode1(
	PaddingModifier,
	[null, null, null, null, null, null, null]
	ModifiedLayoutNode2(
		SizeModifier,
		[DrawModifier, null, null, null, null, null, null]  // 现在 DrawModifier 是表头
		innerLayoutNodeWrapper(
		    [null, null, null, null, null, null, null]
		)
	)
)

现在针对这个场景,只有一个 DrawModifier,它是表头,并且它没有下一个节点了,那么就会执行:

drawEntity.layoutNodeWrapper.performDraw(canvas)

具体处理流程如下:

    ==> ModifiedLayoutNode1 没有 DrawModifier,继续下一个节点 ModifiedLayoutNode2 查找有没有 DrawModifier;

    ==> ModifiedLayoutNode2 有 DrawModifier,DrawModifier.draw() 调用绘制,DrawModifier 链表没有下一个 DrawModifier,继续下一个节点 InnerNodeCoordinator 查找有没有 DrawModifier;

    ==> InnerNodeCoordinator 没有 DrawModifier,绘制结束

  1. 如果 DrawModifier 链表的下一个节点不为 null,说明还有 DrawModifier,从 DrawModifier 链表头开始遍历调用 DrawModifier 处理绘制。

比如:

Box(Modifier.padding(10.dp).background(Color.Red).background(Color.Blue).size(120.dp))

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

ModifiedLayoutNode1(
	PaddingModifier,
	[null, null, null, null, null, null, null]
	ModifiedLayoutNode2(
		SizeModifier,
		[DrawModifier2 -> DrawModifier1, null, null, null, null, null, null]
		innerLayoutNodeWrapper(
		    [null, null, null, null, null, null, null]
		)
	)
)

现在针对这个场景,表头有下一个节点了,那么就会执行:

nextDrawEntity.draw(canvas)

跳转一下看看:

fun draw(canvas: Canvas) {
    ... ...

    val drawScope = layoutNode.mDrawScope
    drawScope.draw(canvas, size, layoutNodeWrapper, this) {
        with(drawScope) {
            with(modifier) {
                draw()
            }
        }
    }
}

这段代码熟悉吗?DrawModifier2 绘制完,调用 drawContent(),让 DrawModifier1 开始绘制。

drawContent() 是通知当前 DrawModifier 下一级的 LayoutNodeWrapper 的 DrawModifier 处理绘制。所以如果有一个 DrawModifier 没有调用 drawContent(),相当于链条断开不会通知内部的 DrawModifier 去绘制。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1398399.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

牛客小白月赛86 解题报告 | 珂学家 | 最大子数组和变体 + lazy线段树动态区间树

前言 整体评价 终于回归小白月赛的内核了&#xff0c;希望以后也继续保持&#xff0c;_. A. 水盐平衡 思路: 模拟 题目保证没有浓度相等的情况 盐度 a/b&#xff0c; c/d 的比较关系 演变为 ad, bc 两者的大小关系 #include <bits/stdc.h>using namespace std;int …

【北京】买套二手房需要多少钱?

上次我们看了苏州和上海的二手房&#xff0c;这次我们一起来看下北京的二手房价格如何。 数据来源 数据来自贝壳二手房&#xff0c;每个区最多获取了3千条房源信息&#xff0c;数据共计4万条左右 对数据感兴趣的朋友&#xff0c;公众号后台发送北京二手房获取数据文件 各区房…

面试之Glide如何绑定Activity的生命周期

Glide绑定Activity生命周期 Glide.with() 下面都是它的重载方法&#xff0c;Context&#xff0c;Activity&#xff0c;FragmentActivity, Fragment, android.app.Fragment fragment,View都可以作为他的参数&#xff0c;内容大同小异&#xff0c;都是先getRetriever&#xff0…

【C++入门到精通】智能指针 shared_ptr 简介及C++模拟实现 [ C++入门 ]

阅读导航 引言一、简介二、成员函数三、使用示例四、C模拟实现五、std::shared_ptr的线程安全问题六、总结温馨提示 引言 在 C 动态内存管理中&#xff0c;除了 auto_ptr 和 unique_ptr 之外&#xff0c;还有一种智能指针 shared_ptr&#xff0c;它可以让多个指针共享同一个动…

关于大模型学习中遇到的3

来源&#xff1a;网络 Embedding模型 随着大型语言模型的发展&#xff0c;以ChatGPT为首&#xff0c;涌现了诸如ChatPDF、BingGPT、NotionAI等多种多样的应用。公众大量地将目光聚焦于生成模型的进展之快&#xff0c;却少有关注支撑许多大型语言模型应用落地的必不可少的Embed…

STM32407用汇顶的GT911触摸芯片调试实盘

这个配置很关键 代码 #include "stm32f4xx.h" #include "GT9147.h" #include "Touch.h" #include "C_Touch_I2C.h" #include "usart.h" #include "delay.h" #include "LCD.h" #incl…

HarmonyOS 页面跳转控制整个界面的转场动画

好 本文 我们来说 页面间的转场动画 就是 第一个界面到另一个界面 第一个界面的退场和第二个界面的进场效果 首先 我这里 创建了两个页面文件 Index.ets和AppView.ets index组件 编写代码如下 import router from "ohos.router" Entry Component struct Index {b…

视频监控需求记录

记录一下最近要做的需求&#xff0c;我个人任务还是稍微比较复杂的 需求&#xff1a;需要实现一个视频实时监控、视频回放、视频设备管理&#xff0c;以上都是与组织架构有关 大概的界面长这个样子 听着需求好像很简单&#xff0c;但是~我们需要在一个界面上显示两个厂商的视…

STM32标准库开发——串口发送/单字节接收

USART基本结构 串口发送信息 启动串口一的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);初始化对应串口一的时钟&#xff0c;引脚&#xff0c;将TX引脚设置为复用推挽输出。 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); GPIO_InitTypeDef GPIO_In…

我们应该了解的⽤户画像

当我们谈⽤户画像时&#xff0c;到底在谈什么 对于互联⽹公司来说&#xff0c;企业的增⻓、内容、活动、产品等⼯作基本上都是围绕着“⽤户”来做的&#xff0c;可以说都是在做“⽤户运营”这个⼯作&#xff0c;⽽⽤户画像是⽤户运营⼯作中⾮常重要的⼀环 ⽤户画像的主要特征是…

Linux命令手册

简介 Multics&#xff08;大而全&#xff09;项目失败&#xff0c;吸取教训启动Unix&#xff08;小而精&#xff09;&#xff0c;Linus Benedict Torvalds受Unix启发开发初始版本Linux内核&#xff0c;Git也由其开发&#xff0c;目的是为了更好的管理Linux内核开发。Unix是商业…

Windows如何部署TortoiseSVN客户端

文章目录 前言1. TortoiseSVN 客户端下载安装2. 创建检出文件夹3. 创建与提交文件4. 公网访问测试 前言 TortoiseSVN是一个开源的版本控制系统&#xff0c;它与Apache Subversion&#xff08;SVN&#xff09;集成在一起&#xff0c;提供了一个用户友好的界面&#xff0c;方便用…

专业130+总分380+哈尔滨工程大学810信号与系统考研经验水声电子信息与通信

今年专业课810信号与系统130&#xff0c;总分380顺利考上哈尔滨工程大学&#xff0c;一年的努力终于换来最后的录取&#xff0c;期中复习有得有失&#xff0c;以下总结一下自己的复习经历&#xff0c;希望对大家有帮助&#xff0c;天道酬勤&#xff0c;加油&#xff01;专业课&…

Java找二叉树的公共祖先

描述&#xff1a; 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一个节…

[AI]文心一言出圈的同时,NLP处理下的ChatGPT-4.5最新资讯

前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff1a;https://www.captainbed.cn/z ChatGPT体验地址 文章目录 前言4.5key价格泄漏ChatGPT4.0使用地址ChatGPT正确打开方式最新功能语音助手存档…

【C语言】编译和链接深度剖析

文章目录 &#x1f4dd;前言&#x1f320; 翻译环境和运行环境&#x1f309;翻译环境 &#x1f320;预处理&#xff08;预编译&#xff09;&#x1f309;编译 &#x1f320;词法分析&#x1f320;语法分析 &#x1f309;语义分析&#x1f320;汇编 &#x1f309; 链接&#x1f…

动态闪图怎么在线合成?仅需三秒在线合成

GIF闪图是一种常见的动态图像格式&#xff0c;它由多个静态图像帧组成&#xff0c;以连续的方式播放&#xff0c;形成动画效果。每个图像帧都可以包含不同的颜色和透明度&#xff0c;因此GIF闪图通常用于展示简单的动画、表情符号或者短视频片段。这种格式在网络上广泛应用&…

论rtp协议的重要性

rtp ps流工具 rtp 协议&#xff0c;实时传输协议&#xff0c;为什么这么重要&#xff0c;可以这么说&#xff0c;几乎所有的标准协议都是国外创造的&#xff0c;感叹一下&#xff0c;例如rtsp协议&#xff0c;sip协议&#xff0c;webrtc&#xff0c;都是以rtp协议为基础&#…

C++中的static(静态)

2014年1月19日 内容整理自The Cherno:C系列 2014年1月20日 内容整理自《程序设计教程&#xff1a;用C语言编程 第三版》 陈家骏 郑滔 -----------------------------------------------------------------------------------------------------------------------------…

【RT-DETR有效改进】利用MobileNetV3替换Backbone(轻量化网络结构,提点)

前言 大家好&#xff0c;这里是RT-DETR有效涨点专栏。 本专栏的内容为根据ultralytics版本的RT-DETR进行改进&#xff0c;内容持续更新&#xff0c;每周更新文章数量3-10篇。 专栏以ResNet18、ResNet50为基础修改版本&#xff0c;同时修改内容也支持ResNet32、ResNet101和PP…