一文了解Compose

一文了解Compose

簡(jiǎn)介

Jetpack Compose 是一個(gè)適用于 Android 的新式聲明性界面工具包。閱讀官方介紹可以了解到,Compose 大概是這么個(gè)東西:

  1. Compose 是一個(gè)聲明性界面框架,使用更少的代碼、強(qiáng)大的工具和直觀的 Kotlin API。
  2. 拋棄了原有安卓view的體系,完全重新實(shí)現(xiàn)了一套新的ui體系
  3. 使用可組合函數(shù)來替換view構(gòu)建UI界面,只允許一次測(cè)量,避免了布局嵌套多次測(cè)量問題,從根本上解決了布局層級(jí)對(duì)布局性能的影響。

看完介紹后第一反應(yīng)便有了以下幾個(gè)疑問。

  1. Compose完全摒棄了View嗎?ComposeUI結(jié)構(gòu)是什么樣?
  2. 可組合函數(shù)與View有什么不同?函數(shù)最終有沒有轉(zhuǎn)換成了view
  3. 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í)高了不少。


image.png

Compose 視圖結(jié)構(gòu)

Comepose拋棄了view體系,那Compose的視圖結(jié)構(gòu)是什么樣的。

打開“顯示布局邊界”可以看到Compose的組件顯示了布局邊界,我們知道,F(xiàn)lutter與WebView H5內(nèi)的組件都是不會(huì)顯示布局邊界的,難道Compose最終還是把函數(shù)變成了View?

邊框.jpg

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

compose_view_layout.png

最上層還是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里是怎么管理它的布局元素的呢。

  1. 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() }
}
  1. 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()
}

  1. 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)
        }
       ... 
    }

  1. 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)
}
自動(dòng)換行.png

測(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鏈。

Modifier鏈.png

LayoutNodeWrapper鏈

我們?cè)谇懊鍸ayout 里看到,modifier(鏈)被傳入materializerOf()方法,最終被賦值給LayoutNote.modifier這個(gè)成員變量。
當(dāng)被set()方法設(shè)置給LayoutNote時(shí) ,

  1. 調(diào)用foldOut()方法遍歷modifier鏈,把不同的Modifier類型包裝成Node,LayoutModifier包裝成ModifiedLayoutNode,
  2. ModifiedLayoutNode 掛在初始節(jié)點(diǎn) innerLayoutNodeWrapper:LayoutNodeWrapper 上,形成一條Wrapper鏈,innerLayoutNodeWrapper在鏈尾。
  3. 遍歷完成后把鏈頭設(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
             ...
        }

畫成圖如下:


measure.png

測(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
    }
    
  1. 測(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
    }
}

  1. 布局
internal fun replace() {
        try {
            relayoutWithoutParentInProgress = true
            outerMeasurablePlaceable.replace()
        } finally {
            relayoutWithoutParentInProgress = false
        }
    }

Layout. replace()方法,看到布局過程也是由outerMeasurablePlaceable完成的。和測(cè)量過程一樣,過程就不一步步的分析了,經(jīng)過一系列的鏈?zhǔn)秸{(diào)用,最終到 InnerPlaaceable.placeAt()。

  1. InnerPlaaceable.placeAt(),設(shè)置 當(dāng)前節(jié)點(diǎn)的位置。

  2. 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()
    }
  1. 繪制
    這些布局是怎么繪制到屏幕上的呢?在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é):

  1. 在setContent的過程中,會(huì)創(chuàng)建ComposeView與AndroidComposeView,其中AndroidComposeView是Compose的入口,連接View體系和新的Compose體系。
  2. Compose雖然沒有了view 樹,但也需要一個(gè)數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)Text、Image等屏幕元素,那就是LayoutNote樹。
  3. 微件里設(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è)量和布局。
  4. Compose 特性禁止在一次Layout 過程中多次測(cè)量一個(gè)子項(xiàng),這從根本上解決了布局嵌套多次測(cè)量的問題。
  5. Compose繪制雖然脫離了View,但還是依賴canvas。

文章到這里結(jié)束了,開頭的那些問題也都有了答案。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容