序言
使用flutter
進(jìn)行開發(fā)也有一段時(shí)間了壤短,今天小轟來聊聊widget
设拟、element
慨仿、renderObject
三者間的結(jié)構(gòu)關(guān)系。
三棵樹
- Widget 樹負(fù)責(zé)配置信息纳胧,我們平時(shí)寫代碼寫的就是這棵樹镰吆。
- RenderObject 樹是渲染樹,負(fù)責(zé)計(jì)算布局跑慕,繪制万皿,F(xiàn)lutter 引擎就是根據(jù)這棵樹來進(jìn)行渲染的。
-
Element 樹作為中間者核行,管理著將 Widget 生成 RenderObject和一些更新操作牢硅。
從上圖可以看出,widget 樹和 Element 樹節(jié)點(diǎn)是一一對(duì)應(yīng)關(guān)系钮科,每一個(gè) Widget 都會(huì)有其對(duì)應(yīng)的 Element唤衫。但是 RenderObject 樹則不然,只有需要渲染的 Widget 才會(huì)有對(duì)應(yīng)的節(jié)點(diǎn)绵脯。
Widget
我們平時(shí)用 Widget 使用聲明式的形式寫出來的界面佳励,可以理解為 Widget 樹,這是要介紹的第一棵樹蛆挫。下面赃承,我們來看看 Widget 的結(jié)構(gòu)設(shè)計(jì):
-
widget 分為兩種類型
widget 從渲染的角度進(jìn)行分類,分為 可渲染W(wǎng)idget
與 不可渲染W(wǎng)idget
悴侵。像我們常用的 statelessWidget 與 statefulWidget 就屬于不可渲染的Widget瞧剖。
-
widget 內(nèi)部結(jié)構(gòu)
如上圖所示:
- 每個(gè)widget都提供
createElement
方法,每個(gè)widget最終都會(huì)轉(zhuǎn)化成Element
可免; - widget被觸發(fā)
build
方法的時(shí)機(jī)特別頻繁抓于,canUpdate
方法維護(hù)Element復(fù)用機(jī)制
。當(dāng)返回true時(shí)浇借,復(fù)用舊的Element捉撮; - 只有可渲染的widget(子類
RenderObjectWidget
)提供生成RenderObject
的方法
Element
與widget
的分類相對(duì)應(yīng),element
也區(qū)分是否可渲染妇垢,繼承關(guān)系如下:
從上圖中我們得知巾遭,StatefulElement 在其構(gòu)造方法中調(diào)用了
widget.createState
方法,并賦值 _state 其 widget 對(duì)象闯估。
這就是 statefuleWidget createState 方法被調(diào)用的時(shí)機(jī)
灼舍。
重點(diǎn):Element 內(nèi)部結(jié)構(gòu)分析
整理總結(jié)
- Element 持有外部 Widget 對(duì)象。
- Element 提供獲取 RenderObject 的方法
(get renderObject)
涨薪。[從自己開始往子節(jié)點(diǎn)遍歷骑素,直到找出RenderObjectElement
,RenderObjectElement 提供生成RenderObject 的能力] - RenderObjectElement 通過調(diào)用
widget.createRenderObject(this)
生成RenderObject
- 核心方法 mount()`
componentElement
的mount方法主要作用是執(zhí)行build(根據(jù)類型區(qū)分widget.build
,state.build
)renderObjectElement
的mount方法主要作用是生成RenderObject- Element創(chuàng)建完成時(shí)就會(huì)調(diào)用
mount
, 調(diào)用順序?yàn)?mount -> _firstBuild -> reBuild -> performRebuild -> build- Element.markNeedsRebuild 會(huì)重新走 reBuild
RenderObject
成員方法介紹:
parentData
: 由父節(jié)點(diǎn)賦值尤辱,父RenderObj會(huì)將子RenderObj的相關(guān)數(shù)據(jù)存儲(chǔ)在子元素的parentData中砂豌。如在 Stack 布局中厢岂,RenderStack就會(huì)將子元素的偏移數(shù)據(jù)存儲(chǔ)在子元素的parentData中(具體可以查看Positioned
實(shí)現(xiàn))。layout()方法
: 接收兩個(gè)參數(shù)阳距,constrains
為父節(jié)點(diǎn)對(duì)子節(jié)點(diǎn)的大小限制塔粒;parentUsesSize
標(biāo)識(shí)本節(jié)點(diǎn)布局發(fā)生變化時(shí)父節(jié)點(diǎn)是否同步發(fā)生重布局操作。_relayoutBoundry
: 在layout()方法中進(jìn)行賦值筐摘,當(dāng)parentUsesSize等于false時(shí)卒茬,_relayoutBoundry = this(當(dāng)前RenderObject對(duì)象),表示它的大小變化不會(huì)影響到parent的大小咖熟。否則圃酵,_relayoutBoundry = = (parent! as RenderObject)._relayoutBoundary;markNeedsLayout()
: 當(dāng)一個(gè)Element標(biāo)記為 dirty 時(shí)便會(huì)重新 build,這時(shí)RenderObject便會(huì)重新布局馍管,我們是通過調(diào)用 markNeedsBuild() 來標(biāo)記Element為 dirty 的郭赐。
從自身開始向parent遍歷,直到找到是 relayoutBoundry 的 RenderObject 為止确沸。然后將其標(biāo)記為 dirty捌锭,重新build。
void markNeedsLayout() { ...省略 if (_relayoutBoundary != this) { markParentNeedsLayout(); } else { ...省略 } }
-
performResize()
: 在layout方法中罗捎,只在sizedByParent
為 true 時(shí)观谦,才會(huì)被調(diào)用。 -
performLayout()
: 在layout方法中被調(diào)用桨菜,每次layout都會(huì)觸發(fā)
題外話豁状,State中setState做了什么?
State.setState() -> _element.markNeedBuild() -> dirty=true -> readerObject.markNeedLayout()