Jetpack Compose - DrawModifier (十三)

語言: CN / TW / HK

初識DrawModifier

先看下面這段程式碼

Box( modifier = Modifier .requiredSize(50.dp) .background(Color.Yellow) .requiredSize(10.dp) ) 應該知道這個黃色的方塊,最終的大小應該是10dp,為啥? 因為background 始終都是會用到他右邊的尺寸大小

可以看下這個background 實際上就是一個DrawModifier

image.png

image.png

看一下 下面的寫法:

``` Box( modifier = Modifier .background(Color.Red) .size(50.dp).drawWithContent {

    }

) ```

跑一下,能看出來 這是一個50dp大小的紅色矩形

但是我如果稍微改一下程式碼,變成下面這樣的: ``` Box( modifier = Modifier .size(50.dp).drawWithContent {

    }.background(Color.Red)

) ```

你就會發現這個紅色的矩形消失了

再改一下:

Box( modifier = Modifier .size(50.dp).drawWithContent { drawContent() }.background(Color.Red) ) 你會發現 這個紅色矩形又回來了,為什麼drawContent方法不呼叫,結果就會出錯呢?

帶著上述的疑惑, 我們跟一下DrawModifier的程式碼,看看 他在compose中的繪製流程中,起到了一些什麼作用,為什麼會造成上述例子中的現象

注意這篇文章需要 LayoutModifier 作為基礎

原始碼分析

看下DrawModifer 是在什麼時候影響佈局的吧

image.png

image.png

這個toWrap,上一節我們介紹過,本質上就是一層層巢狀的ModifierLayoutNode,是一個LayoutNodeWrapper的物件

再看下這個entities 是啥

image.png

image.png

這個LayoutNodeEntitiy 裡面還包裹了一個Modifier,

可以看下他有幾個子類

image.png

這個就很關鍵了,我們再切回去看看

image.png

首先這個DrawEntity就是我們剛才介紹過的,LayoutNodeEntitiy的子類,他包裹了我們的modifer,

看下第二個引數

image.png

這裡可以知道 ,這第二個引數其實固定是0,雖然你看起來他是一個變數,但他本質上還是一個0

再看下這個add函式,一個簡單的連結串列操作

image.png

我們首先看一下這個entities是啥

image.png

image.png

可以看出來,這裡 entities 就是一個長度為6的陣列,

而這個數組裡面 每一個位置上的元素都是固定的比如位置0 上放的永遠就是drawmodifier,有人要問 如果有多個drawmodifer怎麼辦, 這裡的資料結構其實和java中的hashmap非常向,這個陣列中的每個元素 本質上就是一個連結串列,

舉個例子,假設有個3個drawmodifer 組成了這個連結串列 LayoutNodeEntity(drawmodifer3)->LayoutNodeEntity(drawmodifer2)>LayoutNodeEntity(drawmodifer1)

然後這個連結串列就放到了 這個enties的 第0個位置上,僅此而已

搞清楚這個資料結構 其他地方就不難理解了,

下面就繼續看下 draw方法 裡面幹了啥,是如何被DrawModifier影響的

image.png

首先看下draw方法 裡面的layer,這裡你就理解為圖層的意思就可以了, 可以把一個檢視分成幾個獨立的layer,這些layer 疊加以後展示出來的 就是你看到的view的效果

比如這裡的alpha,底層也就是用的layer

image.png

接著看,其實下面這個名字很長的draw函式 在有了前面的分析基礎上,就很好理解了,就是去entities這個數組裡面 取drawmodifer位置上的連結串列, 然後看一下這個連結串列有沒有值,有值就 把內容和drawmodifier一起繪製 沒有值,那就只繪製內容

image.png

我們首先看下這個performDraw函式,這個函式是會在 沒有drawModifier時候被呼叫,這裡的wrapped 是啥? 前面LayoutModifier的文章裡介紹過的,A 包裹B,這個B 就是wrapped, 那為什麼 這裡wrapped還有個判空的問號? 因為最裡層的Node節點 顯然是沒有包裹任何東西的

image.png

而且這個我們看下這個performDraw函式方法裡面 最終還是我們呼叫的我們前面介紹的draw方法,你可以理解為一個遞迴的呼叫邏輯

這裡還應該要注意的是,在compose的體系中,所有元件底層都是用drawmodifier來實現的,比如text元件,image元件,等等

我們接著看,當有drawmodifier的時候 繪製的流程有什麼變化

image.png

image.png

image.png

看上面的呼叫鏈 我們即可知道,繪製的過程其實就是在drawScope.draw 這個方法傳遞的 lambda,也就是那個block

這個lambda裡面 最重要的就是當前這個modifier的draw方法了

image.png

這個draw方法是啥?其實就是那個介面啊

image.png

image.png

換個寫法:

其實就是呼叫的我們寫的drawWithContent這個函式的返回值了

image.png

這裡有人會問,這個drawConent方法不呼叫的話,介面就展示不出來 這是為啥?

image.png

看到這你應該能明白了,因為drawmodifier的呼叫 本質上是一個drawmodifier的連結串列的順序呼叫,

而完成這個順序呼叫的就是這個drawContent方法, 你如果不去呼叫這個drawContent方法 那就意味著

你這個元件的繪製 只繪製了你的head node部分,而剩下的部分你都沒有繪製。。。

再來詳細看一下這個drawContent方法

image.png

這裡就是判斷一下 如果當前節點的drawModifer連結串列上還有節點 那繼續繪製當前節點的drawmodifer 否則就去你包裹的子節點的drawModifer連結串列上去繪製

這麼說好像有點繞了,我們換個說法

我們可以考慮一下 如果我們的介面是如下的結構

image.png

A包裹B包裹C,且每個節點都有自己的DrawModifier連結串列,那他實際上的繪製過程就可以概括為

image.png

注意了, 這裡並不意味著, a1的內容一定會覆蓋掉a2的內容,因為實際上 誰是誰的爹 這件事 是可以調整的

怎麼理解這句話?

看下面這個例子

image.png

一個是先繪製content 再繪製一個圓形,那顯然就是這個圓點會遮蓋住content

反之,則content會遮蓋住這個圓點

一些例子分析

Box( modifier = Modifier.background(Color.Red).background(Color.Blue).size(40.dp) ) 這個Box 會顯示什麼顏色? 顯然是藍色, 因為有了第二小節的分析 我們知道,

先繪製了紅色背景的drawmodifer,然後再繪製了藍色背景的Modifer, 左邊包裹著右邊 藍色自然就覆蓋了紅色,所以最終是藍色

再看下這個例子:

Box( modifier = Modifier.requiredSize(80.dp).background(Color.Blue).requiredSize(10.dp) )

這個藍色的方塊最終有多大?,答案是10dp ,為啥?

image.png

從右往左遍歷的時候 DrawModifer是和toWrap來繫結在一起的, 到這個例子中就是 遍歷到了 藍色方塊的DrawModifier的時候 他就和 toWrap繫結在一起了,這個toWrap 顯然就是那個10dp大小的layoutModifer

簡而言之, drawModifier的繪製大小總是跟隨著 他右邊的layoutModifier

再看最後一個例子

Box( modifier = Modifier.size(40.dp).padding(19.dp).background(Color.Blue) )

這裡藍色的方塊有多大? 應該是一個size為2的 方塊對吧,

但是你要如何得出這個size為2的結論?

前面介紹過 drawModifer也就是這個例子中的background 的大小是跟隨他右邊的layoutModifer的

但是這個例子中右邊沒有了? 既然右邊沒有了 為啥最終大小是2

回顧上一個文章中layoutmodifer的知識,這裡最外層是一個40dp的size,然後包裹著一個padding為19的layout,自然剩下的空間就只有40-19*2=2了, 所以最終的blue 的方塊大小就是2