" 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 开始,首先我们看两个简单的场景:
一个绿色背景的 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?
在之前我们分析 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()?
接下来,我们就带着这两个问题正式进入这篇文章的主题!
我们前面在分析 LayoutModifier 的时候说过,所有组件最终都会被转换为一个 LayoutNode,这个 LayoutNode 包含了所有的测量数据,那同样它也会包含你对组件设定的 Modifier,最终经过一系列转换,也会传到 LayoutNode 里面,那 LayoutNode 里面必然会存在一个 modifer 属性来处理你所设定的所有 Modifier,我们之前看过 LayoutModifer 的处理逻辑了,那么如果是一个 DrawModifier 呢?
正如之前文章所说,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)
到这里,就存在两个问题了:
// 比如,这是我们最开始时候例子里面的 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。
/**
* 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?这个基类的子类都是链表的节点?
带着这两个猜测,我们现在来重点看下 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)
}
这里我们兵分三路:
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()
]
]
value class EntityType<T : LayoutNodeEntity<T, M>, M : Modifier>(val index: Int)
companion object {
val DrawEntityType = EntityType<DrawEntity, DrawModifier>(0)
... ...
}
原来就是一个取值的工作,返回一个固定值:0,这个值有什么用?我们先往下看。
现在我们来看看 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,
]
以此类推,后进来的就插入表头,一个链接一个。
现在我们已经很清楚 LayoutNode 是如何处理 DrawModifier 的了,接下来我们就看看 DrawModifier 是如何对绘制产生精细影响的。
在 LayoutNode 中,绘制是由 draw() 函数处理的。
internal fun draw(canvas: Canvas) = outerLayoutNodeWrapper.draw(canvas)
这里的 outerLayoutNodeWrapper 是什么?
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)
。
// LayoutNodeWrapper.kt
private fun drawContainedDrawModifiers(canvas: Canvas) {
val head = entities.head(EntityList.DrawEntityType)
if (head == null) {
performDraw(canvas)
} else {
head.draw(canvas)
}
}
这段代码的逻辑很清晰,主要就是三步:
val head = entities.head(EntityList.DrawEntityType)
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 就不会进行任何绘制动作。
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 表达式内部的逻辑即可,分两步走:
比如:
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,绘制结束
比如:
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 去绘制。