序言
使用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)是一一對應(yīng)關(guān)系,每一個(gè) Widget 都會(huì)有其對應(yīng)的 Element。但是 RenderObject 樹則不然,只有需要渲染的 Widget 才會(huì)有對應(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的分類相對應(yīng),element也區(qū)分是否可渲染,繼承關(guān)系如下:

從上圖中我們得知,StatefulElement 在其構(gòu)造方法中調(diào)用了
widget.createState方法,并賦值 _state 其 widget 對象。
這就是 statefuleWidget createState 方法被調(diào)用的時(shí)機(jī)。
重點(diǎn):Element 內(nèi)部結(jié)構(gòu)分析

整理總結(jié)
- Element 持有外部 Widget 對象。
- 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)對子節(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對象),表示它的大小變化不會(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()
