Widget、Element、RenderObject 三棵樹關(guān)系結(jié)構(gòu)分析

序言

使用flutter進(jìn)行開發(fā)也有一段時(shí)間了,今天小轟來聊聊widget、elementrenderObject三者間的結(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 分類

  • widget 內(nèi)部結(jié)構(gòu)
widget 內(nèi)部成員

如上圖所示:

  1. 每個(gè)widget都提供createElement方法,每個(gè)widget最終都會(huì)轉(zhuǎn)化成Element;
  2. widget被觸發(fā)build方法的時(shí)機(jī)特別頻繁,canUpdate方法維護(hù)Element復(fù)用機(jī)制。當(dāng)返回true時(shí),復(fù)用舊的Element;
  3. 只有可渲染的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)分析
從左往右觀察內(nèi)部成員及方法
整理總結(jié)
  1. Element 持有外部 Widget 對象。
  2. Element 提供獲取 RenderObject 的方法(get renderObject)。[從自己開始往子節(jié)點(diǎn)遍歷,直到找出 RenderObjectElement,RenderObjectElement 提供生成RenderObject 的能力]
  3. RenderObjectElement 通過調(diào)用 widget.createRenderObject(this)生成RenderObject
  4. 核心方法 mount()`
  1. componentElement的mount方法主要作用是執(zhí)行build(根據(jù)類型區(qū)分widget.build, state.build
  2. renderObjectElement 的mount方法主要作用是生成RenderObject
  3. Element創(chuàng)建完成時(shí)就會(huì)調(diào)用 mount, 調(diào)用順序?yàn)?mount -> _firstBuild -> reBuild -> performRebuild -> build
  4. Element.markNeedsRebuild 會(huì)重新走 reBuild

RenderObject

RenderObject成員結(jié)構(gòu)
成員方法介紹:
  1. parentData: 由父節(jié)點(diǎn)賦值,父RenderObj會(huì)將子RenderObj的相關(guān)數(shù)據(jù)存儲(chǔ)在子元素的parentData中。如在 Stack 布局中,RenderStack就會(huì)將子元素的偏移數(shù)據(jù)存儲(chǔ)在子元素的parentData中(具體可以查看Positioned實(shí)現(xiàn))。

  2. layout()方法: 接收兩個(gè)參數(shù),constrains為父節(jié)點(diǎn)對子節(jié)點(diǎn)的大小限制;parentUsesSize標(biāo)識(shí)本節(jié)點(diǎn)布局發(fā)生變化時(shí)父節(jié)點(diǎn)是否同步發(fā)生重布局操作。

  3. _relayoutBoundry: 在layout()方法中進(jìn)行賦值,當(dāng)parentUsesSize等于false時(shí),_relayoutBoundry = this(當(dāng)前RenderObject對象),表示它的大小變化不會(huì)影響到parent的大小。否則,_relayoutBoundry = = (parent! as RenderObject)._relayoutBoundary;

  4. 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 {
     ...省略
   }
 }
  1. performResize(): 在layout方法中,只在sizedByParent 為 true 時(shí),才會(huì)被調(diào)用。
  2. performLayout(): 在layout方法中被調(diào)用,每次layout都會(huì)觸發(fā)

題外話,State中setState做了什么?

State.setState() -> _element.markNeedBuild() -> dirty=true -> readerObject.markNeedLayout()

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

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

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