" 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)
到这里,就存在两个问题了:
- 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。
- 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)
}
这里我们兵分三路:
- 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()
]
]
- DrawEntityType.index 是啥?
value class EntityType<T : LayoutNodeEntity<T, M>, M : Modifier>(val index: Int)
companion object {
val DrawEntityType = EntityType<DrawEntity, DrawModifier>(0)
... ...
}
原来就是一个取值的工作,返回一个固定值:0,这个值有什么用?我们先往下看。
- 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)
}
}
这段代码的逻辑很清晰,主要就是三步:
- 取 DrawEntity 链表表头
val head = entities.head(EntityList.DrawEntityType)
- 表头如果为空,也就是没有设置过 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 就不会进行任何绘制动作。
- 表头不为空,有设置 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 表达式内部的逻辑即可,分两步走:
- 如果 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,绘制结束
- 如果 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 去绘制。