一文了解Compose
簡(jiǎn)介
Jetpack Compose 是一個(gè)適用于 Android 的新式聲明性界面工具包。閱讀官方介紹可以了解到,Compose 大概是這么個(gè)東西:
- Compose 是一個(gè)聲明性界面框架,使用更少的代碼、強(qiáng)大的工具和直觀的 Kotlin API。
- 拋棄了原有安卓view的體系,完全重新實(shí)現(xiàn)了一套新的ui體系
- 使用可組合函數(shù)來替換view構(gòu)建UI界面,只允許一次測(cè)量,避免了布局嵌套多次測(cè)量問題,從根本上解決了布局層級(jí)對(duì)布局性能的影響。
看完介紹后第一反應(yīng)便有了以下幾個(gè)疑問。
- Compose完全摒棄了View嗎?ComposeUI結(jié)構(gòu)是什么樣?
- 可組合函數(shù)與View有什么不同?函數(shù)最終有沒有轉(zhuǎn)換成了view
- ComposeUI是如何解多次測(cè)量問題問題的?
這篇文章將會(huì)介紹Compose的整體結(jié)構(gòu)并一個(gè)一個(gè)探索這些問題的答案。
基礎(chǔ)概念
compose編程思想
Compose是用Kotlin寫的,Kotlin版本不低于1.5.10;kotlin支持函數(shù)式編程是Compose實(shí)現(xiàn)的關(guān)鍵。簡(jiǎn)介里說了 Compose 是一個(gè)聲明性框架,也就是聲明式或者說函數(shù)式編程。他通過函數(shù)刷新屏幕上的內(nèi)容,而不需要拿到組件的具體實(shí)例,UI是關(guān)于狀態(tài)的函數(shù),一切都是函數(shù)。
Composable注解
Compose函數(shù)都加了Composable注解修飾。Composable注解的函數(shù)只能被另一個(gè)Composable注解的函數(shù)調(diào)用。此注解可告訴編譯器,被修飾的函數(shù)是Compose函數(shù),用來描述UI界面的。這些函數(shù)不返回任何內(nèi)容,他們旨在描述當(dāng)前的UI狀態(tài)。
微件
Text()、Image()、Row()、Coulm()等這些描述屏幕元素的函數(shù)都被稱為微件,類似TextView、ImageView、LinearLayout。從代碼上看 view 之間的關(guān)系是繼承的關(guān)系,LinearLayout 繼承ViewGroup,ViewGroup繼承 View。微件之間沒有任何關(guān)系,通過組合嵌套自由搭配。在繼承關(guān)系中有些不必要的屬性也會(huì)一并繼承,這就顯的多余。在這一點(diǎn)上,組合組合函數(shù)明顯更好。
重組
在view 體系中如需更改某個(gè)組件,可以在該該件上調(diào)用 setter 以更改其內(nèi)部狀態(tài)。在 Compose 中,則是使用新數(shù)據(jù)再次調(diào)用可組合函數(shù)。在Compose中UI刷新的唯一方法就是重組,決定是否重組的條件就是與@Composable元素綁定的數(shù)據(jù)是否發(fā)生了變化。
簡(jiǎn)單使用
實(shí)現(xiàn)一個(gè)recycleView
@Composable
fun showList() {
//生產(chǎn) 100條數(shù)據(jù)
var datalist: MutableList<String> = mutableListOf()
for (i: Int in 0..100) {
datalist.add("$i")
}
//縱向滑動(dòng)容器
LazyColumn {
//頂部吸頂布局
stickyHeader {
Text(text = "頂部吸頂布局",
color = Color.Cyan,
modifier = Modifier
.fillMaxWidth()
.background(Color.White),
fontSize = 20.sp,
textAlign = TextAlign.Center
)
}
//構(gòu)建列表
items(datalist) { name ->
//構(gòu)建一個(gè)item
BindItemView(name = name)
}
}
}
@Composable
fun BindItemView(name: String) {
var imageURL =
"https://img0.baidu.com/it/u=1766591091,2326601705&fm=253&fmt=auto&app=120&f=JPEG?w=1200&h=674"
//橫向布局 包含文本和圖片
Row(modifier = Modifier
.padding(10.dp)
//Item點(diǎn)擊事件
.clickable {
Toast
.makeText(mContext, name, Toast.LENGTH_LONG)
.show()
}) {
//縱向文本布局
Column(
modifier = Modifier
.background(Color.White)
.weight(1f)
) {
Text(text = "我是個(gè)標(biāo)題$name", fontSize = 16.sp, maxLines = 1)
Text(
color = Purple200,
modifier = Modifier.padding(top =6.dp, bottom = 4.dp)
.background(Color.White),
text = "我是個(gè)內(nèi)容,我可能很長(zhǎng),但是我只能顯示最多兩行",
fontSize = 14.sp,
maxLines = 2
)
Text(text = "userName", fontSize = 10.sp)
Spacer(modifier = Modifier.padding(top = 4.dp).fillMaxWidth().height(1.dp).background(Color.Gray))
}
//加載一個(gè)網(wǎng)絡(luò)圖片
Image(
modifier = Modifier
.size(80.dp, 60.dp)
.align(Alignment.CenterVertically),
contentScale = ContentScale.Crop,
painter = rememberImagePainter(imageURL),
contentDescription = ""
)
}
}
上述60行代碼中的Ui,包含了一個(gè)縱向滾動(dòng)的list布局,item有文本有圖片,有點(diǎn)擊事件,還有頂部吸頂效果。沒有xml沒有adapter,相比xml-view體系,代碼簡(jiǎn)潔度確實(shí)高了不少。

Compose 視圖結(jié)構(gòu)
Comepose拋棄了view體系,那Compose的視圖結(jié)構(gòu)是什么樣的。
打開“顯示布局邊界”可以看到Compose的組件顯示了布局邊界,我們知道,F(xiàn)lutter與WebView H5內(nèi)的組件都是不會(huì)顯示布局邊界的,難道Compose最終還是把函數(shù)變成了View?

通過android studio 的LayoutInspector看到ComposeActivity的布局結(jié)構(gòu)

最上層還是DecorView、FrameLayout,然后就看到有一個(gè)AndroidComposeView,沒有TextView或者其他的View了。ComposeActivity 的onCreate方法里setContent替換了原來的setContentView,點(diǎn)擊去我們看到
public fun ComponentActivity.setContent(
parent: CompositionContext? = null,
content: @Composable () -> Unit
) {
//decorView 的第一個(gè)子View如果是 ComposeView 直接用,如果不是就創(chuàng)建一個(gè)ComposeView ,然后添加到跟布局
val existingComposeView = window.decorView
.findViewById<ViewGroup>(android.R.id.content)
.getChildAt(0) as? ComposeView
if (existingComposeView != null) with(existingComposeView) {
...
setContent(content)
} else ComposeView(this).apply {
...
setContent(content)
...
setOwners()
setContentView(this, DefaultActivityContentLayoutParams)
}
}
decorView 的第一個(gè)子View如果不是ComposeView就創(chuàng)建一個(gè) ,然后添加到跟布局。而ComposeView 里又通過 Wrapper_android.kt 創(chuàng)建了一個(gè) AndroidComposeView,我們前面見過。ComposeView 和 AndroidComposeView 都是繼承ViewGroup,都是在setContent時(shí)添加進(jìn)去的。除此之外我們寫的Column,Row,Text并沒有出現(xiàn)在布局層級(jí)中,也就是說Compose 并沒有把函數(shù)轉(zhuǎn)成View。AndroidComposeView 可以理解是一個(gè)連接view 和 compose 的入口。
我們知道,View系統(tǒng)通過一個(gè)View樹的數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)TextView,ImageView等屏幕元素,渲染的時(shí)候去遍歷View樹。Compose里是怎么管理它的布局元素的呢。
- LayoutNote
我們點(diǎn)開任何一個(gè)微件函數(shù),一系列調(diào)用最終都會(huì)到了Layout.kt的Layout()方法,Layout() 核心是調(diào)用ReusableComposeNode ()方法。這里有個(gè)參數(shù) factory,factory 是一個(gè)構(gòu)造器函數(shù), factory 被調(diào)用就會(huì)創(chuàng)建一個(gè)LayoutNote,定義的布局屬性modifier也在這里設(shè)置給了LayoutNode。每個(gè)微件最終都是一個(gè)LayoutNote。
@Composable inline fun Layout(
content: @Composable () -> Unit,
modifier: Modifier = Modifier,
measurePolicy: MeasurePolicy
) {
...
ReusableComposeNode<ComposeUiNode, Applier<Any>>(
//1. factory 指向 ComposeUiNode的構(gòu)造器,創(chuàng)建一個(gè)LayoutNode
factory = ComposeUiNode.Constructor,
update = {
set(measurePolicy, ComposeUiNode.SetMeasurePolicy)
set(density, ComposeUiNode.SetDensity)
set(layoutDirection, ComposeUiNode.SetLayoutDirection)
set(viewConfiguration, ComposeUiNode.SetViewConfiguration)
},
//2. modifier設(shè)置給LayoutNode
skippableUpdate = materializerOf(modifier),
content = content
)
}
ComposeUiNode #{
val Constructor: () -> ComposeUiNode = LayoutNode.Constructor
}
LayoutNode #{
internal val Constructor: () -> LayoutNode = { LayoutNode() }
}
- Composables :ReusableComposeNode方法里又調(diào)用Composer 來插入或者更新LayoutNote。
inline fun <T, reified E : Applier<*>> ReusableComposeNode(
noinline factory: () -> T,
update: @DisallowComposableCalls Updater<T>.() -> Unit,
noinline skippableUpdate: @Composable SkippableUpdater<T>.() -> Unit,
content: @Composable () -> Unit
) {
if (currentComposer.applier !is E) invalidApplier()
currentComposer.startReusableNode()
//插入一個(gè)新節(jié)點(diǎn)
if (currentComposer.inserting) {
currentComposer.createNode(factory)
} else {
//復(fù)用節(jié)點(diǎn)
currentComposer.useNode()
}
currentComposer.disableReusing()
//更新節(jié)點(diǎn)
Updater<T>(currentComposer).update()
currentComposer.enableReusing()
SkippableUpdater<T>(currentComposer).skippableUpdate()
currentComposer.startReplaceableGroup(0x7ab4aae9)
content()
currentComposer.endReplaceableGroup()
currentComposer.endNode()
}
- Composer :這里 factory方法才被執(zhí)行創(chuàng)建了一個(gè)note,獲取插入節(jié)點(diǎn)的位置,調(diào)用nodeApplier插入到root里。
override fun <T> createNode(factory: () -> T) {
...
//1. 獲取插入節(jié)點(diǎn)的位置
val insertIndex = nodeIndexStack.peek()
val groupAnchor = writer.anchor(writer.parent)
groupNodeCount++
recordFixup { applier, slots, _ ->
@Suppress("UNCHECKED_CAST")
//2. 在這里才執(zhí)行了 factory方法, 創(chuàng)建了一個(gè)note
val node = factory()
slots.updateNode(groupAnchor, node)
@Suppress("UNCHECKED_CAST") val nodeApplier = applier as Applier<T>
//3. 調(diào)用nodeApplier插入到root里。
nodeApplier.insertTopDown(insertIndex, node)
applier.down(node)
}
...
}
- Applier
Composer 里的 nodeApplier 是一個(gè)Applier接口,實(shí)現(xiàn)類是UiApplier,note 最終是在這里add到 root 節(jié)點(diǎn) (LayoutNote) 的 _foldedChildren 列表里。
總結(jié)一下,compose 構(gòu)建了一個(gè)LayoutNode樹,每一個(gè)微件函數(shù)會(huì)生成一個(gè)LayoutNode,Composition 作為起點(diǎn),發(fā)起首次的 構(gòu)圖,通過 Composer 的執(zhí)行填充 NodeTree。渲染引擎基于 LayoutNote 渲染 UI, 每當(dāng)重構(gòu)發(fā)生時(shí),都會(huì)通過 Applier 對(duì) NodeTree 進(jìn)行更新。簡(jiǎn)單來說就是 Composer 和 Applier 一起維護(hù)了一個(gè) LayoutNote 樹。
Compose在渲染時(shí)不會(huì)轉(zhuǎn)化成View,而是有一個(gè)AndroidComposeView作為入口View,我們聲明的Compose布局在渲染時(shí)會(huì)轉(zhuǎn)化成LayoutNode,所有的屏幕元素都存儲(chǔ)在這顆樹上。AndroidComposeView 中會(huì)觸發(fā)LayoutNode的布局與繪制。
compose界面只允許一次測(cè)量。
我們知道View體系有個(gè)問題, 如果父布局的布局屬性是wrap_content、子布局是match_parent ,父布局先以0為強(qiáng)制寬度測(cè)量子View、然后繼續(xù)測(cè)量剩下的其他子View,再用其他子View里最寬的寬度,二次測(cè)量這個(gè)match_parent的子 View。子View測(cè)量了兩次,子view也會(huì)對(duì)下面的子view進(jìn)行兩次測(cè)量 。并且隨著布局的增長(zhǎng)測(cè)量次數(shù)成指數(shù)增長(zhǎng),Compose是如何處理的?
開頭里的例子里我們用了一個(gè)線性視圖容器Row,我們先試著自定義個(gè)AutoRow,實(shí)現(xiàn)自動(dòng)換行的橫向布局。以Row為樣板,照葫蘆畫瓢。
定義一個(gè)@Composable 方法:AutoRow
@Composable
fun AutoRow(modifier: Modifier, content: @Composable () -> Unit) {
//實(shí)例一個(gè) measurePolicy 定義測(cè)量和布局策略
var measurePolicy = object : MeasurePolicy{
//實(shí)現(xiàn) measure 方法,
// Measurable : 是一個(gè)接口,通過斷點(diǎn)可以看到,它的實(shí)例就是一個(gè) LayoutNote,childs 就是子節(jié)點(diǎn)列表。
// constraints : 是父布局的約束條件,有點(diǎn)像View 的 MeasureSpec
override fun MeasureScope.measure(
childs: List<Measurable>,
constraints: Constraints
): MeasureResult {
var placeables = mutableListOf<Placeable>()
childs.forEach {
//1.遍歷測(cè)量子項(xiàng),得到一個(gè)可放置項(xiàng) Placeable
// measure()方法傳入一個(gè)Constraints:約束。 測(cè)量完成后返回 一個(gè) Placeable
// 一次測(cè)量過程中,多次調(diào)用一個(gè)子項(xiàng)的measure()方法會(huì)拋異常
var constraints = Constraints(0, constraints.maxWidth, 0, constraints.maxHeight)
val placeable = it.measure(constraints)
placeables.add(placeable)
}
var layoutX = 0
var layoutY = 0
var padding = 20
//2.layout 方法確定布局的大小,把測(cè)量得到的Placeable擺放到指定位置
val measureResult = layout(width = constraints.minWidth, height = constraints.minHeight,placementBlock={
placeables.forEach { placeable ->
if (layoutX + placeable.width > constraints.maxWidth) {
layoutX = 0
layoutY += placeable.height
}
// 3.定位子項(xiàng)在父布局位置的位置
placeable.placeRelative(layoutX, layoutY)
layoutX += placeable.width + padding
}
})
return measureResult
}
}
//4.布局
//上面這些事定義了measure()、layout() 的策略,Layout這里才觸發(fā)執(zhí)行的。
Layout(content = content, modifier = modifier, measurePolicy = measurePolicy)
}

測(cè)量的 measure () 方法最終調(diào)用了 outerMeasurablePlaceable.measure(),其中check()方法里判斷了當(dāng)前節(jié)點(diǎn)是否正在被父布局測(cè)量,如果正在測(cè)量就會(huì)拋出異常。
override fun measure(constraints: Constraints) =
outerMeasurablePlaceable.measure(constraints)
// OuterMeasurablePlaceable ##
override fun measure(constraints: Constraints): Placeable {
// when we measure the root it is like the virtual parent is currently laying out
val parent = layoutNode.parent
if (parent != null) {
check(
layoutNode.measuredByParent == LayoutNode.UsageByParent.NotUsed ||
@Suppress("DEPRECATION") layoutNode.canMultiMeasure
) {
"measure() may not be called multiple times on the same Measurable. Current " +
"state ${layoutNode.measuredByParent}. Parent state ${parent.layoutState}."
}
...
}
其實(shí)Measurable的注釋講的很清楚
Measures the layout with constraints, returning a Placeable layout that has its new size. A Measurable can only be measured once inside a layout pass.
使用約束測(cè)量布局,返回具有新大小的可放置布局。一個(gè)Measurable 只能測(cè)量一次在一次layout 過程里。
這是Compose的特性,就只允許一次測(cè)量,從根本上解決了布局嵌套多次測(cè)量的問題。
測(cè)量過程分析
Modifier 鏈
我們構(gòu)造一個(gè)compose 微件時(shí),有一個(gè)參數(shù)很重要,他描述了這個(gè)微件的一些特性。比如:Text()通過 Modifier設(shè)置了size,padding,background等等。
Text( modifier = Modifier
.size(80.dp, 20.dp)
.padding(10.dp)
.background(Color.White),
text = "我是個(gè)內(nèi)容,我可能很長(zhǎng),但是我只能顯示最多兩行",
)
fun Modifier.size(width: Dp, height: Dp) = this.then(
SizeModifier(
minWidth = width,
maxWidth = width,
minHeight = height,
maxHeight = height,
enforceIncoming = true,
inspectorInfo = debugInspectorInfo {
name = "size"
properties["width"] = width
properties["height"] = height
}
)
)
infix fun then(other: Modifier): Modifier =
if (other === Modifier) this else CombinedModifier(this, other)
class CombinedModifier(
private val outer: Modifier,
private val inner: Modifier
) : Modifier {...}
size(),padding()可以理解成操作符,類似rxjava 里的那樣。點(diǎn)進(jìn)去會(huì)看到在這些方法里創(chuàng)建了對(duì)應(yīng)的 SizeModifier,PaddingModifier,他們都繼承自LayoutModifier(表示Layout相關(guān)的屬性),background 里創(chuàng)建了Background 繼承DrawModifier(表示繪制相關(guān)的屬性),LayoutModifier、DrawModifier 繼承Modifier.Element。SizeModifier,PaddingModifier、Background被包進(jìn)CombinedModifier,形成了一條Modifier鏈。

LayoutNodeWrapper鏈
我們?cè)谇懊鍸ayout 里看到,modifier(鏈)被傳入materializerOf()方法,最終被賦值給LayoutNote.modifier這個(gè)成員變量。
當(dāng)被set()方法設(shè)置給LayoutNote時(shí) ,
- 調(diào)用foldOut()方法遍歷modifier鏈,把不同的Modifier類型包裝成Node,LayoutModifier包裝成ModifiedLayoutNode,
- ModifiedLayoutNode 掛在初始節(jié)點(diǎn) innerLayoutNodeWrapper:LayoutNodeWrapper 上,形成一條Wrapper鏈,innerLayoutNodeWrapper在鏈尾。
- 遍歷完成后把鏈頭設(shè)置給outerMeasurablePlaceable.outerWrapper。
override var modifier: Modifier = Modifier
set(value) {// 成員變量modifier 的set()方法
...
//1. foldOut()遍歷 modifier 每個(gè)節(jié)點(diǎn)
val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod, toWrap ->
// 2. 類型判斷,構(gòu)建Node節(jié)點(diǎn),掛在初始節(jié)innerLayoutNodeWrapper上
if (mod is DrawModifier) {
val drawEntity = DrawEntity(toWrap, mod)
drawEntity.next = toWrap.drawEntityHead
toWrap.drawEntityHead = drawEntity
drawEntity.onInitialize()
}
//toWrap = innerLayoutNodeWrapper
var wrapper = toWrap
...
//省略了一些Modifier類型
// 不同的Modifier類型被包裝成不同類型的Node,
//LayoutModifier被包裝成了ModifiedLayoutNode,
if (mod is FocusEventModifier) {
wrapper = ModifiedFocusEventNode(wrapper, mod)
.initialize()
.assignChained(toWrap)
}
if (mod is FocusRequesterModifier) {
wrapper = ModifiedFocusRequesterNode(wrapper, mod)
.initialize()
.assignChained(toWrap)
}
if (mod is FocusOrderModifier) {
wrapper = ModifiedFocusOrderNode(wrapper, mod)
.initialize()
.assignChained(toWrap)
}
if (mod is LayoutModifier) {
wrapper = ModifiedLayoutNode(wrapper, mod)
.initialize()
.assignChained(toWrap)
}
...
//3. 返回鏈頭
wrapper
}
outerWrapper.wrappedBy = parent?.innerLayoutNodeWrapper
//outerWrapper是鏈頭,innerLayoutNodeWrapper在鏈尾
outerMeasurablePlaceable.outerWrapper = outerWrapper
...
}
畫成圖如下:

測(cè)量入口
Layout()里把modifier設(shè)置給了LayoutNode,還把modifier轉(zhuǎn)成了LayoutNodeWrapper鏈,那什么時(shí)候使用這些Modifier的呢。我們開始的地方知道了ComposeActivity布局的頂層還是View,那就是說視圖的最頂層的渲染還是依賴view的。在 AndroidComposeView 看到了 dispatchDraw(),這就是Compose 渲染的入口。這里會(huì)觸發(fā)布局測(cè)量。
override fun dispatchDraw(canvas: android.graphics.Canvas) {
//Compose 測(cè)量與布局入口
measureAndLayout()
//Compose 繪制入口
canvasHolder.drawInto(canvas) { root.draw(this) }
//...
}
測(cè)量布局過程
measureAndLayout()遍歷所有需要測(cè)量的 LayoutNote,最終到MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded()方法,測(cè)量并布局每一個(gè)LayoutNote。
private fun remeasureAndRelayoutIfNeeded(layoutNode: LayoutNode): Boolean {
var sizeChanged = false
if (layoutNode.isPlaced ||
layoutNode.canAffectParent ||
layoutNode.alignmentLines.required
) {
if (layoutNode.layoutState == NeedsRemeasure) {
//1. 測(cè)量 layoutNode
sizeChanged = doRemeasure(layoutNode)
}
if (layoutNode.layoutState == NeedsRelayout && layoutNode.isPlaced) {
//2. 設(shè)置 layoutNode 位置
if (layoutNode === root) {
layoutNode.place(0, 0)
} else {
layoutNode.replace()
}
onPositionedDispatcher.onNodePositioned(layoutNode)
consistencyChecker?.assertConsistent()
}
。。。
}
return sizeChanged
}
- 測(cè)量
doRemeasure() 調(diào)用了 outerMeasurablePlaceabl.remeasure(),
outerMeasurablePlaceabl 是 LayoutNote 的一個(gè)成員,outerMeasurablePlaceabl 又調(diào)用outerWrapper.measure()。上面說到過,outerWrapper 是頭節(jié)點(diǎn) LayoutModifierNode 。
internal class ModifiedLayoutNode(
wrapped: LayoutNodeWrapper,
modifier: LayoutModifier
) : DelegatingLayoutNodeWrapper<LayoutModifier>(wrapped, modifier) {
override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
with(modifier) {
measureResult = measureScope.measure(wrapped, constraints)
this@ModifiedLayoutNode
}
}
根據(jù)我們寫的代碼,此時(shí)modifier 為 SizeModifier, 類型 LayoutModifier.kt
SizeModifier.kt
private class SizeModifier(
private val minWidth: Dp = Dp.Unspecified,
private val minHeight: Dp = Dp.Unspecified,
private val maxWidth: Dp = Dp.Unspecified,
private val maxHeight: Dp = Dp.Unspecified,
private val enforceIncoming: Boolean,
inspectorInfo: InspectorInfo.() -> Unit
) : LayoutModifier, InspectorValueInfo(inspectorInfo) {
override fun MeasureScope.measure(
measurable: Measurable,/*下一個(gè)LayoutNodeWrapper*/
constraints: Constraints/* 父容器或者上一個(gè)節(jié)點(diǎn)的約束 */
): MeasureResult {
val wrappedConstraints = targetConstraints.let { targetConstraints ->
if (enforceIncoming) {//當(dāng)我們給控件指定大小時(shí),這個(gè)值就為true
//結(jié)合父容器或者上一個(gè)節(jié)點(diǎn)的約束 和我們指定約束進(jìn)行結(jié)合生成一個(gè)新的約束
constraints.constrain(targetConstraints)
} else {
……
}
}
// 進(jìn)行下一個(gè) LayoutNodeWrapper 節(jié)點(diǎn)測(cè)量
val placeable = measurable.measure(wrappedConstraints)
//測(cè)量完,開始擺放節(jié)點(diǎn)的位置
return layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
}
}
SizeModifier.measure自己測(cè)量完,進(jìn)行下一個(gè) LayoutNodeWrapper 節(jié)點(diǎn)的測(cè)量,一直到最后 InnerPlaceable 節(jié)點(diǎn)。InnerPlaceable觸發(fā)LayoutNode子項(xiàng)的測(cè)量,這里就是Layout 的MeasurePolicy 的measure 執(zhí)行的地方了
class InnerPlaceable{
override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
val measureResult = with(layoutNode.measurePolicy) {
// 觸發(fā)LayoutNode子項(xiàng)的測(cè)量,這里就是Layout 的MeasurePolicy 的measure 執(zhí)行的地方了
layoutNode.measureScope.measure(layoutNode.children, constraints)
}
layoutNode.handleMeasureResult(measureResult)
return this
}
}
- 布局
internal fun replace() {
try {
relayoutWithoutParentInProgress = true
outerMeasurablePlaceable.replace()
} finally {
relayoutWithoutParentInProgress = false
}
}
Layout. replace()方法,看到布局過程也是由outerMeasurablePlaceable完成的。和測(cè)量過程一樣,過程就不一步步的分析了,經(jīng)過一系列的鏈?zhǔn)秸{(diào)用,最終到 InnerPlaaceable.placeAt()。
InnerPlaaceable.placeAt(),設(shè)置 當(dāng)前節(jié)點(diǎn)的位置。
onNodePlaced()最終會(huì)調(diào)用 MeasureResult.placeChildren(),觸發(fā)子項(xiàng)位置的擺放。
override fun placeAt(
position: IntOffset,
zIndex: Float,
layerBlock: (GraphicsLayerScope.() -> Unit)?
) {
//1.往下掉用LayoutNoteWrapper.placeAt(),設(shè)置當(dāng)前節(jié)點(diǎn)的 的位置
super.placeAt(position, zIndex, layerBlock)
//2. 最終會(huì)調(diào)用 MeasureResult.placeChildren(),設(shè)置子項(xiàng)的位置。
layoutNode.onNodePlaced()
}
- 繪制
這些布局是怎么繪制到屏幕上的呢?在AndroidComposeView里看到繪制的入口是
canvasHolder.drawInto(canvas) { root.draw(this) }
root是LayoutNode的根節(jié)點(diǎn)。調(diào)用了LayoutNode.draw()方法,還傳了個(gè)參數(shù):canvas。LayoutNode繼續(xù)調(diào)用outerLayoutNodeWrapper.draw(canvas),看到這應(yīng)該能猜到了,繪制應(yīng)該也是LayoutNodeWrapper完成的。還記得backgroung方法創(chuàng)建了BackGround繼承DrawModifier,在LayoutNode里被包裝成了DrawEntity
internal class DrawEntity(
val layoutNodeWrapper: LayoutNodeWrapper,//下一個(gè)節(jié)點(diǎn)
val modifier: DrawModifier
) : OwnerScope {
最終還是調(diào)用到了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)
}
}
我們看到繪制還是基于canvas的。
總結(jié):
- 在setContent的過程中,會(huì)創(chuàng)建ComposeView與AndroidComposeView,其中AndroidComposeView是Compose的入口,連接View體系和新的Compose體系。
- Compose雖然沒有了view 樹,但也需要一個(gè)數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)Text、Image等屏幕元素,那就是LayoutNote樹。
- 微件里設(shè)置的modifier會(huì)構(gòu)建一個(gè)LayoutModifier鏈,在設(shè)置進(jìn)LayoutNode時(shí)轉(zhuǎn)換成了LayoutNodeWrapper鏈,測(cè)量和布局是由一個(gè)個(gè)LayoutNodeWrapper節(jié)點(diǎn)按順序完成的,最后一個(gè)節(jié)點(diǎn)InnerPlaaceable觸發(fā)子項(xiàng)的測(cè)量和布局。
- Compose 特性禁止在一次Layout 過程中多次測(cè)量一個(gè)子項(xiàng),這從根本上解決了布局嵌套多次測(cè)量的問題。
- Compose繪制雖然脫離了View,但還是依賴canvas。
文章到這里結(jié)束了,開頭的那些問題也都有了答案。