close

作者:FunnySaltyFish https://juejin.cn/post/7063451846861406245

總覽

Jetpack Compose 中,單個可組合項被顯示出來,總體上經歷三個過程

Composition(組合) -> Layout(布局) -> Drawing(繪製) 。

其中Layout階段又存在兩個方面的內容:Measure(測量) 和 Place(擺放)

今天我們主要着眼於 Layout 階段,看看各個 Composable 是如何正確確定各自位置和大小的

Layout階段主要做三件事情:

測量所有子微件的大小
確定自己的大小
正確擺放所有子元素的位置

為簡化說明,我們先給出一個簡單例子。該例子中,所有元素只需要遍歷一次。

如下圖的 SearchResult 微件,它的構成如下:

現在我們來看看Layout過程在這個例子中是什麼情況

Measure

請求測量根布局,即 Row

Row 為了知道自己的大小,就得先知道自己的子微件有多大,於是請求 Image 和 Column 測量它們自己。對於Image,由於它內部沒有其他微件,所以它可以完成自身測量過程並返回相關位置指令

接下來是 Column,因為它內部有兩個 Text,於是請求子微件測量。而對於 Text,它們也會正確返回自己的大小和位置指令

這時 Column 大小和位置指令即可正確確定。最後,Row 內部所有測量完成,它可以正確獲得自己的大小和位置指令

測量階段到此結束,接下來就是正確的擺放位置了

Place

完成測量後,微件就可以根據自身大小從上至下執行各子微件的位置指令,從而確定每個微件的正確位置。

現在我們把目光轉向 Composition 階段。大家平時寫微件,內部都是由很多更基本的微件組合而來的,而事實上,這些基本的微件還有更底層的組成部分。如果我們展開剛剛的那個例子,它就成了這個樣子

在這裡,所有的葉節點(即沒有子元素的節點)都是 Layout 這個微件。我們來看看這個微件吧

Layout Composable

此微件的簽名如下:

@ComposableinlinefunLayout(content:@Composable()->Unit,modifier:Modifier=Modifier,measurePolicy:MeasurePolicy)

我們先看看第三個參數,這是之前從未見過的東西;而它恰恰控制着如何確定微件大小以及它們的擺放策略。

那來寫個例子吧。我們現在自定義一個簡單的縱向布局,也就是低配版 Column

自定義布局 - 縱向布局

寫個框架

funVerticalLayout(modifier:Modifier=Modifier,content:@Composable()->Unit){Layout(modifier=modifier,content=content){measurables:List<Measurable>,constrains:Constraints->}}

Measurable 代表可測量的,其定義如下:

interfaceMeasurable:IntrinsicMeasurable{/***Measuresthelayoutwith[constraints],returninga[Placeable]layoutthathasitsnew*size.A[Measurable]canonlybemeasuredonceinsidealayoutpass.*/funmeasure(constraints:Constraints):Placeable}

可以看到,這是個接口,唯一的方法 measure 返回 Placeable,接下來根據這個 Placeable 擺放位置。而參數 measurables 其實也就是傳入的子微件形成的列表

而 Constraints 則描述了微件的大小策略,它的部分定義摘錄如下:

舉個栗子,如果我們想讓這個微件想多大就多大(類似 match_parent),那我們可以這樣寫:

如果它是固定大小(比如長寬50),那就是這樣寫

接下來我們就先獲取 placeable 吧

valplaceables=measurables.map{it.measure(constrains)}

在這個簡單的例子中,我們不對 measure 的過程進行過多干預,直接測完獲得有大小的可放置項。

接下來確定我們的 VerticalLayout 的寬、高。對於咱們的布局,它的寬應該容納的下最寬的孩子,高應該是所有孩子之和。於是得到以下代碼:

//寬度:最寬的一項valwidth=placeables.maxOf{it.width}//高度:所有子微件高度之和valheight=placeables.sumOf{it.height}

最後,我們調用 layout 方法返回最終的測量結果。前兩個參數為自身的寬高,第三個 lambda 確定每個 Placeable 的位置

layout(width,height){vary=0placeables.forEach{it.placeRelative(0,y)y+=it.height}}

這裡用到了 Placeable.placeRelative 方法,它能夠正確處理從右到左布局的鏡像轉換

一個簡單的 Column 就寫好了。試一下?

funrandomColor()=Color(Random.nextInt(255),Random.nextInt(255),Random.nextInt(255))@ComposablefunCustomLayoutTest(){VerticalLayout(){(1..5).forEach{Box(modifier=Modifier.size(40.dp).background(randomColor()))}}}

嗯,工作基本正常。

接下來我們實現一個更複雜一點的:簡易瀑布流

自定義布局—簡易瀑布流

先把基本的框架擼出來,在這裡只實現縱向的,橫向同理

@ComposablefunWaterfallFlowLayout(modifier:Modifier=Modifier,content:@Composable()->Unit,columns:Int=2//橫向幾列){Layout(modifier=modifier,content=content,){measurables:List<Measurable>,constrains:Constraints->TODO()}}

我們加入了參數columns用來指定有幾列。由於瀑布流寬度是確定的,所以我們需要手動指定寬度

valitemWidth=constrains.maxWidth/2valitemConstraints=constrains.copy(minWidth=itemWidth,maxWidth=itemWidth)valplaceables=measurables.map{it.measure(itemConstraints)}

在這裡我們用新的 itemConstraints 對子微件的大小進行約束,固定了子微件的寬度。接下來就是擺放了。瀑布流的擺放方式其實就是看看當前哪一列最矮,就把當前微件擺到哪一列,不斷重複就行

代碼如下:

@ComposablefunWaterfallFlowLayout(modifier:Modifier=Modifier,columns:Int=2,//橫向幾列content:@Composable()->Unit){Layout(modifier=modifier,content=content,){measurables:List<Measurable>,constrains:Constraints->valitemWidth=constrains.maxWidth/columnsvalitemConstraints=constrains.copy(minWidth=itemWidth,maxWidth=itemWidth)valplaceables=measurables.map{it.measure(itemConstraints)}//記錄當前各列高度valheights=IntArray(columns)layout(width=constrains.maxWidth,height=constrains.maxHeight){placeables.forEach{placeable->valminIndex=heights.minIndex()placeable.placeRelative(itemWidth*minIndex,heights[minIndex])heights[minIndex]+=placeable.height}}}}

這裡用到了一個自定義的拓展函數 minIndex,作用是尋找數組中最小項的索引值,代碼很簡單,如下:

funIntArray.minIndex():Int{vari=0varmin=Int.MAX_VALUEthis.forEachIndexed{index,e->if(e<min){min=ei=index}}returni}

效果如下(設置列數為3):

本文代碼:

https://github.com/FunnySaltyFish/JetpackComposeStudy/tree/master/app/src/main/java/com/funny/compose/study/ui/post_layout

~ END ~


推薦閱讀

探索 Compose 內核:深入 SlotTable 系統
玩轉 Compose 動畫:手把手教你實現微博點讚彩虹特效
Compose 打造炫酷的星球打卡 App

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()