天天看點

Jetpack Compose 測量流程源碼分析

本文你将了解到什麼

通過一段代碼場景,以Layout 函數為入口分析源碼,解答一些心中所惑,如通過 Modifier 設定大小是如果起作用的?MeasurePolicy 接口的 measure 方法是怎麼調用的?布局中的測量流程是什麼樣的?控件是怎麼确認大小的?

回顧

在 JetPack Compose 手寫一個 Row 布局 | 自定義布局 一文中我們已經了解了如何自定義 Layout,使用 Layout 函數即可。

@Composable inline fun Layout(
    content: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {}
           

我們可以通過參數 modifier 給布局指定大小,在 measurePolicy 中對 children 進行測量和布置,布局的 children 寫在 content 函數中。使用起來很友善嘛😁 ,但是好奇心讓我想知道這個Layout裡面做了些什麼?🤔 這裡面的源碼可能很複雜,但還是想嘗試着看一看,不試一試怎麼知道呢。 🚀

為了友善探究和調試代碼,本文以下面代碼為場景進行分析。

@Composable
private fun ParentLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
  //布局的測量政策
    val measurePolicy = MeasurePolicy { measurables, constraints ->
        //1.測量 children
        val placeables = measurables.map { child ->
            child.measure(constraints)
        }

        var xPosition = 0
        //2.放置 children
        layout(constraints.minWidth, constraints.minHeight) {
            placeables.forEach { placeable ->
                placeable.placeRelative(xPosition, 0)
                xPosition += placeable.width
            }
        }
    }
    //代碼分析入口
    Layout(content = content, modifier = modifier, measurePolicy = measurePolicy)
}

@Composable
private fun ChildLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
    //...代碼和ParentLayout類似
}

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
           ParentLayout(
                Modifier
                    .size(100.dp)
                    .padding(10.dp)
                    .background(Color.Blue)
            ) {
                ChildLayout {
                    Box {}
                }
                ChildLayout {}
            }
        }
    }
}
           

本次探索希望能回答下面幾個問題

  1. ParentLayout 中 通過 modifier 設定大小是如何起到作用的 ?
  2. MeasurePolicy 接口的 measure 方法是怎麼調用的?他的參數值是怎麼來的呢?
  3. 布局中的測量流程是什麼樣的?

下面就帶着上面這些問題,在看源碼的過程中嘗試去解釋這些問題。

本文源碼對應版本 compose_version = ‘1.0.0-rc01’

跟蹤 Modifier & MeasurePolicy

為了友善跟蹤代碼,我來給代碼設定點跟蹤器 (别搞丢了)😜 下面的代碼中 modifier 參數達到的位置我會用📍 标記, measurePolicy 到達的位置用 📌 标記

Layout.kt → Layout 函數源碼

@Composable inline fun Layout(
    content: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {
    val density = LocalDensity.current
    val layoutDirection = LocalLayoutDirection.current
    ReusableComposeNode<ComposeUiNode, Applier<Any>>(
        factory = ComposeUiNode.Constructor,
        update = {
            set(measurePolicy, ComposeUiNode.SetMeasurePolicy) // 👈  📌 measurePolicy 在這
            set(density, ComposeUiNode.SetDensity)
            set(layoutDirection, ComposeUiNode.SetLayoutDirection)
        },
        skippableUpdate = materializerOf(modifier), // 👈  📍 modifier 在這
        content = content 
    )
}
           

從上面源碼可以看出,Layout 函數體中沒有做什麼處理,核心内容就是調用 ReusableComposeNode 函數。

@Composable 注解的函數建議首字母大寫已區分普通函數,看代碼的時候總覺的 ReusableComposeNode 是個類,點進去發現它是個 Composable 函數 😂 。

Composables.kt → ReusableComposeNode 函數

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.startNode()
    if (currentComposer.inserting) {
        currentComposer.createNode(factory)
    } else {
        currentComposer.useNode()
    }

    //執行update函數
    Updater<T>(currentComposer).update() // 👈  📌 measurePolicy 在這

     //執行skippableUpdate函數
    SkippableUpdater<T>(currentComposer).skippableUpdate()  // 👈  📍 modifier 在這函數中

    currentComposer.startReplaceableGroup(0x7ab4aae9)
    content()
    currentComposer.endReplaceableGroup()
    currentComposer.endNode()
}
           

先隻看和 modifier 有關的,即

SkippableUpdater<T>(currentComposer).skippableUpdate()

這句話,其他的先不看。

這裡函數的參數都是函數參數類型,如果 Kotlin 不太熟悉的,可能看着比較暈 😵 ,下面為了友善分析一下,就把代碼給它攤平了。

這裡的 “代碼給它攤平了” 是指去掉函數回調和一層層的調用,直接寫到一起,避免函數跳來跳去,友善解釋。下文中提到的攤平都是這個意思。

下面我就試着把它攤平看看。

//源代碼
SkippableUpdater<T>(currentComposer).skippableUpdate() 

//0️⃣ 根據 ComposeNode 傳入參數知
skippableUpdate=materializerOf(modifier) // 👈  📍 modifier 

//1️⃣ materializerOf 函數傳回值就是函數類型 SkippableUpdater<ComposeUiNode>.() -> Unit
internal fun materializerOf(
    modifier: Modifier 
): @Composable SkippableUpdater<ComposeUiNode>.() -> Unit = {
     //  📍 這裡隻是對 modifier鍊中存在的 ComposedModifier 進行處理一下,傳回值還是 Modifier
    val materialized = currentComposer.materialize(modifier) 
    update { set(materialized, ComposeUiNode.SetModifier)  } // 👈  📍 modifier
}

//結合代碼 0️⃣ 和代碼 1️⃣ 可知 SkippableUpdater<T>(currentComposer).skippableUpdate() 
//<=> 等價于代碼如下
val skippableUpdater=SkippableUpdater<ComposeUiNode>(currentComposer)
val materialized = currentComposer.materialize(modifier)
skippableUpdater.update { set(materialized, ComposeUiNode.SetModifier)  }
           

好像還差點還有個update 還是沒有攤平

inline class SkippableUpdater<T> constructor(
    @PublishedApi internal val composer: Composer
) {
    inline fun update(block: Updater<T>.() -> Unit) {
        composer.startReplaceableGroup(0x1e65194f)
        Updater<T>(composer).block()
        composer.endReplaceableGroup()
    }
}

//結合SkippableUpdater 的update 函數,skippableUpdater.update { set(materialized, ComposeUiNode.SetModifier) }
// <=>等價于👇
composer.startReplaceableGroup(0x1e65194f)
// 2️⃣ 📍 modifier 最終傳給了這個 set 方法
Updater<ComposeUiNode>(currentComposer).set(materialized, ComposeUiNode.SetModifier)
composer.endReplaceableGroup()
           

代碼 2️⃣ 這裡是調用了一個 set 方法,直接看有點暈,調來調用去,分析起來太多了,不能跑偏了,直接說重點。

companion object {
        val Constructor: () -> ComposeUiNode = LayoutNode.Constructor
     	//ComposeUiNode.SetModifier
        val SetModifier: ComposeUiNode.(Modifier) -> Unit = { this.modifier = it }
    }
// ComposeUiNode.SetModifier 也是個函數類型,調用 set(materialized, ComposeUiNode.SetModifier) 
//最終會觸發SetModifier 函數的執行也就是
	this.modifier=materialized //📍 modifier
 //	👆 this是LayoutNode 對象 是通過觸發 ComposeUiNode.Constructor建立的
           

關于從

set(materialized, ComposeUiNode.SetModifier)

是如何到觸發 SetModifier 函數的,這裡我就分析了,可以通過 debug 很容易驗證這一結論。如果你真的想去分析如何執行的話,分析之前建議先看一下 深入詳解 Jetpack Compose | 實作原理 這篇文章。(友情提醒,如何真要分析這段别陷進去了,别忘記我們看源碼的目的。)

通過上面的分析,我們追蹤的 modifier 被指派給了 LayoutNode 成員的 modifier ,這種是個指派語句,在 kotlin 相當于調用的成員變量的set 方法 LayoutNode.kt

override var modifier: Modifier = Modifier
        set(value) {
            // …… code
            field = value
            // …… code

            // 建立新的 LayoutNodeWrappers 鍊
            // foldOut 相當于周遊 modifier
            val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod /*📍 modifier*/ , toWrap ->
                var wrapper = toWrap
                if (mod is OnGloballyPositionedModifier) {
                    onPositionedCallbacks += mod
                }
                if (mod is RemeasurementModifier) {
                    mod.onRemeasurementAvailable(this)
                }

                val delegate = reuseLayoutNodeWrapper(mod, toWrap)
                if (delegate != null) {
                    wrapper = delegate
                } else {
                      // …… 省略了一些 Modifier判斷 
                      if (mod is KeyInputModifier) {
                        wrapper = ModifiedKeyInputNode(wrapper, mod).assignChained(toWrap)
                    }
                    if (mod is PointerInputModifier) {
                        wrapper = PointerInputDelegatingWrapper(wrapper, mod).assignChained(toWrap)
                    }
                    if (mod is NestedScrollModifier) {
                        wrapper = NestedScrollDelegatingWrapper(wrapper, mod).assignChained(toWrap)
                    }
                    // 布局相關的 Modifier
                    if (mod is LayoutModifier) {
                        wrapper = ModifiedLayoutNode(wrapper, mod).assignChained(toWrap)
                    }
                    if (mod is ParentDataModifier) {
                        wrapper = ModifiedParentDataNode(wrapper, mod).assignChained(toWrap)
                    }

                }
                wrapper
            }

            outerWrapper.wrappedBy = parent?.innerLayoutNodeWrapper
            // 代碼 0️⃣
            outerMeasurablePlaceable.outerWrapper = outerWrapper // 👈  📍 modifier

            ……
        }
           

**👆 代碼片段-1 **

上述代碼主要是将Modifier 鍊轉換LayoutNodeWrapper 鍊的過程,通過Modifier 的 foldOut 函數 周遊Modifier 鍊上的所有元素,并根據不同的Modifier 建立不同的 LayoutNodeWrapper。關于Modifier 的foldOut 函數的作用不懂的可以看我之前寫的 Modifier源碼,Kotlin高階函數用的真6 這篇文章。

在上面的代碼中根據 Modifier 類型建立不同的 LayoutNodeWrapper,這些不同的 Modifier 都是 Modifier.Element 的直接實作類或接口,如 KeyInputModifier、PointerInputModifier、LayoutModifier 等。上面代碼都是 if 判斷,沒有else,也就是說如果 Modifier 不在這些類别範圍内就沒法建立對應的LayoutNodeWrapper,也就相等于我們設定的 Modifier 沒有用。是以我自定義Modifer 一定要在這個類型範圍内,否則是沒有用的。在JetPack Compose 内置的Modifier.Element 子類或接口如下。(Tips. Android studio 檢視類的繼承關系 菜單欄Navgate-> Type Hierarchy ; 快捷鍵 Ctrl+H )
Jetpack Compose 測量流程源碼分析

換個思路繼續跟蹤

上面分析到那裡路好像斷了, 沒法繼續了。思考一下這裡隻是分析了Layout 函數執行時,隻是初始化的準備工作。它的大小和位置如果确認等操作這裡似乎沒有執行。我們剛才是把 ParentLayout 當做父容器來看待的,父容器一般是管理自己的 children 的大小和位置,換一種思路,ParentLayout 出來做父容器,它也可以作為 child 呀,如下面代碼情況。

setContent {
    ParentLayout{
        Box() {}
        // 👇 可以看做是 上面 ParentLayout 的 child,也可以看做是下面 ChildLayout 的父容器
        ParentLayout(
            Modifier
                .size(100.dp)
                .padding(10.dp)
                .background(Color.Blue)
        ) {
            ChildLayout(Modifier.size(100.dp)) {}
        }       
    }
}
           

下面就從 ParentLayout 布局作為 child 的時候來分析一下,如果作為 child 那麼分析入口就應該它從的父容器 MeasurePolicy 的 measure 函數開始分析了。

val measurePolicy = MeasurePolicy { measurables, constraints ->
        val placeables = measurables.map { child ->
            //代碼 0️⃣    
            child.measure(constraints)
        }
       ……
    }
           

代碼 0️⃣ 進行調用 child 的測量方法,從函數參數來看,隻知道 child 是個 Measurable 的類型,但 Measurable 是個接口,我們需要知道 child 具體是 Measurable 那個實作類,我們才好分析 measure 函數的邏輯

Jetpack Compose 測量流程源碼分析

👆 圖檔-0

通過debug 的方式,可以看出 child 是 LayoutNode 對象(為什麼是LayoutNode 下面分析就知道了),那麼就去看看 LayoutNode 的measure函數。

LayoutNode.kt → measure 函數

override fun measure(constraints: Constraints) =
        outerMeasurablePlaceable.measure(constraints)
           

LayoutNode 的 measure 調用了 outerMeasurablePlaceable 的 measure 函數,這個 outerMeasurablePlaceable **代碼片段-1 代碼 0️⃣ **也出現了

outerMeasurablePlaceable.outerWrapper = outerWrapper // 👈 📍 modifier

而且這個

outerMeasurablePlaceable

的屬性

outerWrapper

就包含

modifier

資訊。我們又找到了 modifier 的藏身之處,好像又找到些線索。我們繼續跟蹤代碼吧。

LayoutNodeWrapper 鍊中的測量流程分析⛓

OuterMeasurablePlaceable.kt

override fun measure(constraints: Constraints): Placeable {
        ……
        remeasure(constraints)
        return this
    }

     fun remeasure(constraints: Constraints): Boolean {
        val owner = layoutNode.requireOwner()
         ……
        if (layoutNode.layoutState == LayoutState.NeedsRemeasure ||
            measurementConstraints != constraints
        ) {
            measuredOnce = true
            layoutNode.layoutState = LayoutState.Measuring
            measurementConstraints = constraints
            val outerWrapperPreviousMeasuredSize = outerWrapper.size
            owner.snapshotObserver.observeMeasureSnapshotReads(layoutNode) {
                outerWrapper.measure(constraints)//  0️⃣ 👈  📍 modifier
            }
            layoutNode.layoutState = LayoutState.NeedsRelayout
           ……
            return sizeChanged
        }
        return false
    }
           

👆 代碼片段-2 代碼 0️⃣ 處 我們看到包含 modifier 資訊的 outerWrapper 調用了 它的 measure 方法。outerWrapper 是 LayoutNodeWrapper 類型的,它就是在代碼片段1 處根據不同 Modifer 建立的 LayoutNodeWrapper 鍊。我們給 ParentLayout 的 Modifer 設定為 Modifier.size(100.dp).padding(10.dp).background(Color.Blue) 。那麼對應的LayoutNodeWrapper 鍊如下圖所示

Jetpack Compose 測量流程源碼分析

👆 圖-1

由 圖-1 知代碼片段-2 處的代碼outerWrapper 為 ModifiedLayoutNode 類型。 ModifiedLayoutNode

internal class ModifiedLayoutNode(
    wrapped: LayoutNodeWrapper,
    modifier: LayoutModifier
) : DelegatingLayoutNodeWrapper<LayoutModifier>(wrapped, modifier) {

    override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
        with(modifier) {   👈  📍 modifier
            measureResult = measureScope.measure(wrapped, constraints)
            [email protected]
        }
    }

    protected inline fun performingMeasure(constraints: Constraints, block: () -> Placeable
    ): Placeable {
        measurementConstraints = constraints
        val result = block()
        layer?.resize(measuredSize)
        return result
    }

 ……   
}
           

由 圖-1 知代碼此時的 modifier 為 SizeModifier 類型 LayoutModifier.kt

interface LayoutModifier : Modifier.Element {
	fun MeasureScope.measure(
        measurable: Measurable,/*下一個 LayoutNodeWrapper 節點*/
        constraints: Constraints/* 來着父容器或者來着上一個節點的限制 */
    ): MeasureResult
}
           

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) {
    private val Density.targetConstraints: Constraints
        get() {/*更加我們指定的大小生成對應的限制*/}

    override fun MeasureScope.measure(
        measurable: Measurable,/*下一個LayoutNodeWrapper*/
        constraints: Constraints/* 來着父容器或者來着上一個節點的限制 */
    ): MeasureResult {
        val wrappedConstraints = targetConstraints.let { targetConstraints ->
            if (enforceIncoming) {//當我們給控件指定大小時,這個值就為true
            	//結合父容器或者上一個節點的限制 和我們指定限制進行結合生成一個新的限制
                constraints.constrain(targetConstraints)
            } else {
                ……
            }
        }
        //代碼 0️⃣ 進行下一個 LayoutNodeWrapper 節點測量
        val placeable = measurable.measure(wrappedConstraints)
        //所有節點測量完,開始放置
        return layout(placeable.width, placeable.height) {
            placeable.placeRelative(0, 0)
        }
    }
           

代碼 0️⃣ 繼續進行下一個 LayoutNodeWrapper 節點的測量,一直到最後 InnerPlaceable 節點。

class LayoutNode{
     internal val innerLayoutNodeWrapper: LayoutNodeWrapper = InnerPlaceable(this)
     private val outerMeasurablePlaceable = OuterMeasurablePlaceable(this, innerLayoutNodeWrapper)
     ……
    internal val children: List<LayoutNode> get() = _children.asMutableList()
 }
           

InnerPlaceable

class InnerPlaceable{   
    override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
        val measureResult = with(layoutNode.measurePolicy) {
            // 這裡就是Layout 的MeasurePolicy 的measure 執行的地方了
            layoutNode.measureScope.measure(layoutNode.children, constraints)
        }
        layoutNode.handleMeasureResult(measureResult)
        return this
    }
}
           

這裡就是 Layout 的 MeasurePolicy 的measure 執行的地方了,然後children 繼續執行上述流程了如下圖所示,這樣“MeasurePolicy 接口的 measure 方法是怎麼調用的?他的參數值是怎麼來的呢?”這個問題也就解答了。

Jetpack Compose 測量流程源碼分析

分析解答問題

通過上面的分析,我們大緻可以回答“布局中的測量流程是什麼樣的?” 這個問題了。**

1 準備階段:

child 在父容器在聲明時,也就是調用了 Layout 函數,進行初始化準備操作,記錄這個child 測測量政策,這個child 的children 等,根據設定的 Modifier 鍊建立對應的 LayoutNodeWrapper 鍊。

2 測量階段

child 在父容器的測量政策 MeasurePolicy 的 measure 函數中執行 child 的 measure 函數。接着按照準備好的 LayoutNodeWrapper 鍊一步步的執行各個節點的 measure 函數,最終走到 InnerPlaceable 的 measure 函數,在這個又會繼續它的 children 進行測量,此時它的 children 就會和它一樣進行執行上述流程,一直到所有children 測量完成。 用下面這張圖總結一下上述流程。

Jetpack Compose 測量流程源碼分析

👆 圖-2 測量流程圖

還有一個最後一個問題 ParentLayout 中 通過 modifier 設定大小是如何起到作用的 ? 答:我們通過 Modifer.size() 函數 建構了一個SizeModifer 對象

fun Modifier.size(size: Dp) = this.then(
    SizeModifier(
        minWidth = size,maxWidth = size,
        minHeight = size, maxHeight = size,
        enforceIncoming = true,...
    )
)
           

通過上面的分析我們知道,在測量流程中SizeModifer 的measure 函數會觸發

Jetpack Compose 測量流程源碼分析

SizeModifer部分源碼

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) {
    // 0️⃣ 根據我們設定的大小生成一個限制對象
    private val Density.targetConstraints: Constraints
    get() {
        //控制 maxWidth值範圍
        val maxWidth = if (maxWidth != Dp.Unspecified) {
            maxWidth.coerceAtLeast(0.dp).roundToPx()
        } else {
            Constraints.Infinity
        }

        //maxHeight、minWidth、minHeight 也類似處理一下值的範圍,防止我們瞎搗亂
		...code...

        return Constraints(
            minWidth = minWidth,
            minHeight = minHeight,
            maxWidth = maxWidth,
            maxHeight = maxHeight
        )
    }

    override fun MeasureScope.measure(
        measurable: Measurable,//LayoutNodeWrapper鍊上的下一個節點的
        constraints: Constraints//父容器或者是LayoutNodeWrapper鍊上的上一個節點的constraints
    ): MeasureResult {
        val wrappedConstraints = targetConstraints.let { targetConstraints ->
                if (enforceIncoming) {
                    //1️⃣  和我們設定的大小繼續比較計算,得出一個新的Constraints對象
                    // constrain 函數作用是,在constraints範圍内,得到一個盡可能滿足targetConstraints的大小範圍的限制
                    constraints.constrain(targetConstraints)
                } else {
					//暫不讨論
                }
                //2️⃣  使用新的限制繼續下一個測量
            val placeable = measurable.measure(wrappedConstraints)

            return layout(placeable.width, placeable.height) {
                placeable.placeRelative(0, 0)
            }
        }
    }

    ...
}
           

我們通過Modifier.size 函數設定的大小資訊最終變成SizeModifier 一個成員變量 targetConstraints,它是一個Constraints 類型,主要描述寬度的最小值和最大值(minWidth、maxWidth) 以及高度的最小值最大值(minHeight、maxHeight)。 在 measure 函數中,在代碼 1️⃣ 處把傳入的 constraints 和我們傳入的大小進行大小比較得到一個合适的 Constraints ,然後用新的 Constraints 進行下一個次測量。我們設定的大小就是在這裡起到作用的,影響着接下來的測量限制參數。

補充點-布局寬高計算流程

上面我們把measure 測量流程走了一遍,測量的目的就是為了布局的寬高計算,那這些計算的過程是在哪裡執行的呢?我們通過 Jetpack Compose 寫界面,我們的寫每一個元件都會轉換成對應的節點 LayoutNoe,在Layout 函數中就有一個建立LayoutNode的過程。我們想分析局的最終寬高,就看 LayoutNode 的寬高怎麼來的就行了。

class LayoutNode{
    private val outerMeasurablePlaceable = OuterMeasurablePlaceable(this, innerLayoutNodeWrapper)

    override val width: Int get() = outerMeasurablePlaceable.width

    override val height: Int get() = outerMeasurablePlaceable.height
}
           

看LayoutNode 源碼可知,LayoutNode 的寬高就是 OuterMeasurablePlaceable 的寬高,我們找到 OuterMeasurablePlaceable 的寬高确認就可以了,上面我已經把測量流程大緻分析了一下,結合 圖-2 測量流程圖 再把流程走一下就可以找到答案了,這裡我就不在一段一段代碼的去分析了,我把 OuterMeasurablePlaceable 的寬高計算代碼流程繪制成下面圖檔了,有興趣的可以結合圖檔去看源碼分析一遍。

Jetpack Compose 測量流程源碼分析

👆 圖-3 節點寬高計算代碼流程圖

大緻流程是,在父容器的MeasurePolicy 進行 child 的測量->進入OuterMeasurablePlaceable的測量函數,在這裡先根據函數傳入的Constraints限制計算一下寬高,然後沿着LayoutNodeWrapper 鍊去測量,鍊的每一個節點根據上一個節點傳來的限制進行一次寬高計算,一直到最後的InnerPlaceable的測量,它同樣是根據傳入的限制計算寬高,然後會測量 children,測量完是以的 chlidren之後得到一個測量結果,根據這個測量結果,InnerPlaceable 再一次進行計算寬高。然後把最終的寬高資訊傳回給上一個節點,上一個節點根據傳回的測量結果資訊重新計算寬高,沿着 LayoutNodeWrapper 鍊反向的傳回測量結果,每個節點重新計算後再把結果傳回給上一級,一直到 OuterMeasurablePlaceable,它再拿到測量結果重新計算寬高 OuterMeasurablePlaceable 的寬高就是 LayoutNode 的寬高,整個寬高确認的流程大緻就是這樣了。

Jetpack Compose 測量流程源碼分析

👆 節點寬高簡易流程圖

每一個節點都會根據Constraints 限制計算記一次寬高,同時也把這個限制條件記錄了下來,然後根據測量的結果再一次計算寬高,仔細看計算寬高的邏輯你就會發現,大緻的原因是這樣的,先是按照限制的最小值計算寬高,比如限制是

Constraints(minWidth = 80, maxWidth = 120, minHeight = 90, maxHeight = 150)

那麼此時寬高值分别是80,90。經過測量傳回寬高結果是100,120,拿這個值和原來記錄的限制進行比較,發現寬高的值都在對應的限制範圍内,那最終的寬高值就以測量結果的寬高值為準,如果測量結果不在限制範圍内,那寬高就取限制條件中對應的最小值或最大值。記錄限制條件的目的就是防止測量結果超過此限制。

總結回顧

本文通過追蹤Layout 源碼,通過分析解答了一些問題,下面簡單再回顧一下。

1.布局中通過 modifier 設定大小是如何起到作用的 ?

答:是在 ModifiedLayoutNode 的測量函數中調用 LayoutModifier 的測量函數中起到作用的,比如 SizeModifer 的 measure 函數。詳細分析見上文。

2.MeasurePolicy 接口的 measure 方法是怎麼調用的?

答:是在 InnerPlaceable 的measure 函數中調用的。詳細分析見上文。

3.布局中的測量流程是什麼樣的及寬高的确認?

答:見圖-2 測量流程圖和圖-3 節點寬高計算代碼流程圖。詳細分析見上文。

最後

小編學習提升時,順帶從網上收集整理了一些 Android 開發相關的學習文檔、面試題、Android 核心筆記等等文檔,希望能幫助到大家學習提升,如有需要參考的可以直接去我 CodeChina位址:https://codechina.csdn.net/u012165769/Android-T3 通路查閱。

Jetpack Compose 測量流程源碼分析
Jetpack Compose 測量流程源碼分析
Jetpack Compose 測量流程源碼分析