Flutter視圖基礎簡介--Widget、Element、RenderObject

  前言:Flutter官方文檔裏的一句話:you build your UI out of widgets(使用Flutter開發UI界面時,都是使用Widget),然而,Widget並非咱們真正看到的視圖,背後到底是什麼?其實Flutter Framework提供了三種視圖樹,即:Widget  Element  RenderObject,只不過,咱們使用Flutter開發界面時,一般只和widget打交道,就如前文中所展現的Materail風格或者Cupertino(IOS風格)的各類Widget,然而Flutter界面開發是一種響應式編程,而且Widget都是immutable的,那麼,真正的渲染,刷新,佈局這些問題是誰來處理呢?本文就來了解一下除了Widget,還有哪些基礎類在背後支撐Widget的快速輕量渲染;html

  Widget部分:

看下官網對Widget的定義和描述:Describes the configuration for an Element. (爲Element提供配置信息),從這一句話,隱隱感受到Widget和Element是緊密關聯着的,而且給了Element某些信息,難道是老闆和員工的關係?老闆給員工發個指令,具體的事情都讓員工去作了?咱們再嘗試着從官方文檔中提取一些詳細信息,先來看看到底什麼是Widget?
咱們先來看下Widget這個類中都包含哪些屬性:
 
 1:KEY key
 2:int hasCode
 3:TYPE runtimeType
 
Widget是用戶界面的一部分,而且是不可變的(immutable)。Widget會被inflate到Element,並由Element管理底層渲染樹。Widget自己沒有可變狀態(全部的字段必須是final)。若是想要把可變狀態與Widget關聯起來,可使用StatefulWidget,StatefulWidget經過使用StatefulWidget.createState方法建立State對象,並將之擴充到Element以及合併到樹中;

 

因此,Widget並不會直接管理狀態及渲染,而是經過State這個對象來管理狀態,下篇文章會主要講到State這個對象;

 

給定的Widget能夠被包含在樹中(零次或屢次)。一個給定的Widget能夠放置在樹中屢次,好比:多個TextWidget。每次將一個Widget放入樹中時,它都會被擴充到一個Element中,這也意味着屢次併入樹中的Widget將會被屢次擴充進對應的element。

 

   好比在實際界面開發當中,一個UI視圖樹可能包含有多個TextWidget(Widget可能被使用屢次),可是這些都叫做TextWidget的widget卻被填充(inflate)進一個個獨立的Element中;

 

Widget中的Key這個屬性控制一個Widget如何替換樹中的另外一個Widget。若是兩個Widget的runtimeType和key屬性相等(==),則新的widget經過更新Element(即經過使用新的Widget調用Element.update)來替換舊的Widget。不然,若是兩個Widget的runtimeType和key屬性不相等,則舊的Element將從樹中被移除,新的Widget將被擴充到一個新的Element中,這個新的Element將被插入樹中。

 這裏主要涉及到Widget的更新和移除,插入等操做,在執行此操做前,會用Widget的兩個屬性:runtimeType和key來進行對比判斷;算法

  Element部分

Flutter建立Element的可見樹,相對於Widget來講,是可變的,一般的Flutter界面開發中,咱們不用直接操做Element,而是由框架層實現內部邏輯;就如一個UI視圖樹中,可能包含有多個TextWidget(Widget被使用屢次),可是放在內部視圖樹的視角,這些TextWidget都是填充到一個個獨立的Element中;編程

一樣,咱們先來看一下Element這個類中的屬性:框架

Element property表格
property Type Desc implement
depth int 樹根Element的深度必須大於0
int get depth => _depth;
dirty bool 若是Element已經被標註成須要重建,返回true
bool get dirty => _dirty;
hashCode int  
@overrideint get hashCode => _cachedHash;
owner BuildOwner 管理Element生命週期
@overrideBuildOwner get owner => _owner;
renderObject RenderObject 若是此對象是RenderObjectElement,則渲染對象是樹中此位置處的對象。不然,這個getter將沿着樹走下去,直到找到一個RenderObjectElement。  
size Size  省略  省略
slot    省略  省略
widget Widget 這個Element的配置信息
@overrideWidget get widget => _widget;
runtimeType      
能夠從Element的屬性中看到renderObject和widget,說明Element持有這兩個類的實例;
再來看看文檔中對於Element的介紹:An instantiation of a Widget at a particular location in the tree.  Element是在樹中特定位置Widget的實例;
這裏進一步描述了Widget與Element之間的關係:Element是Widget的實例,也就是說,Element纔是真正幹活的員工,執行老闆戰略部署;咱們接着看文檔中的描述:
Widget描述如何配置子樹,但因爲Widget是不可變的(immutable),所以可使用相同的Widget同時配置多個子樹。Element表示Widget配置樹中的特定位置的實例。隨着時間的推移,與給定Element關聯的Widget可能隨時會發生變化,例如,若是父Widget重建併爲此位置建立新的Widget。Element構成一棵樹。大多數Element都有一個惟一的子Element,可是一些Widget(例如RenderObjectElement的子類)能夠有多個子Element。
 
 
Element具備如下生命週期:
  • 框架層經過調用即將被用來做爲Element的初始化配置信息的Widget的Widget.createElement方法來建立Element; 
  • 框架層經過調用mount方法來將新建立的Element添加到給定父級中給定槽點的樹上。 mount方法負責將任何Widget擴充到Widget並根據須要調用attachRenderObject,以將任何關聯的渲染對象附加到渲染樹上。
  • 此時,Element被視爲「激活的」,並可能出如今屏幕上。
     
  • 在某些狀況下,父(Element)可能會更改用於配置此Element的Widget,例如由於父Element從新建立了新狀態。發生這種狀況時,框架層將調用新的Widget的update方法。新Widget將始終具備與舊Widget相同的runtimeType和key屬性。若是父Element但願在樹中的此位置更改Widget的runtimeType或key,能夠經過unmounting(卸載)此Element並在此位置擴充新Widget來實現。
  • 在某些時候,祖先Element可能會決定從樹中移除該Element(或中間祖先Element),祖先Element本身經過調用deactivateChild來完成該操做。停用中間祖先將從渲染樹中移除該Element的渲染對象,並將此Element添加到owner屬性中的非活動元素列表中,從而讓框架層調用deactivate方法做用在此Element上。
  • 此時,該Element被視爲「無效狀態」,而且不會出如今屏幕上。一個Element能夠保持」非活動"狀態,直到當前動畫幀結束。在動畫幀結束時,任何仍處於非活動狀態的Element都將被卸載。
  • 若是Element被從新組合到樹中(例如,由於它或其祖先之一有一個全局鍵(global key)被重用),框架層將從owner屬性中的非活動Element列表中移除該Element,並調用該Element的activate方法,並從新附加Element的渲染對象到渲染樹。 (此時,Element再次被視爲「活動狀態」並可能出如今屏幕上。)
  • 若是Element在當前動畫幀的末尾沒有被從新組合到樹中,則框架層將調用該元素的unmount方法。
  • 此時,該元素被視爲「已停用」,而且未來不會併入樹中。

 

由此咱們可知:Element存放Widget上下文,經過遍歷視圖樹,Element 同時持有 Widget 和 RenderObject;ide

  RenderObject部分

官網定義:An object in the render tree.渲染樹中的一個對象。從其名字,咱們能夠很直觀地知道,它就是負責渲染的工做;
RenderObject的屬性太多,且該類的細節涉及不少渲染知識,咱們會在後面的系列中再詳細說明其工做原理,在此先繼續看下官網對其描述: RenderObject類的層次結構是渲染庫的核心。
RenderObjects有一個父級,並有一個名爲parentData的插槽,其中父級RenderObject能夠存儲特定於子級的數據,例如子級位置。 RenderObject類也實現了基本的佈局和繪製協議。
可是,RenderObject類沒有定義子模型(例如,節點是否有零個,一個或多個子節點)。它也沒有定義座標系(例如,子級是否位於笛卡爾座標系,極座標系等)或特定的佈局協議(例如佈局是寬度高度仍是尺寸約束或者父級在子級佈置以前仍是以後設置子級的大小和位置等;或者確實是否容許子級讀取他們父級的parentData插槽)。 RenderBox子類引入佈局系統使用笛卡爾座標。
編寫一個RenderObject子類
在大多數狀況下,RenderObject自己的子類化過分,RenderBox將是一個更好的起點。可是,若是渲染對象不想使用笛卡爾座標系,那麼它應該直接從RenderObject繼承。這容許它經過使用約束的新子類而不是使用BoxConstraints來定義本身的佈局協議,而且可能使用全新的一組對象和值來表示輸出的結果而不只僅是一個Size。這種增長的靈活性的代價是沒法依賴RenderBox的功能。例如,RenderBox實現了一個內在的尺寸調整協議,它容許您在沒有徹底鋪設的狀況下測量一個子級,以這樣的方式,若是該子級改變了尺寸,父級將再次佈置(考慮到子級的新尺寸)。這是一個微妙的和容易出錯的功能。
編寫RenderBox的大多數方面也適用於編寫RenderObject,所以推薦先閱讀RenderBox的相關討論。主要區別在於佈局和命中測試,由於這些是RenderBox主要專一的方面。

Layout

佈局
佈局協議從約束的子類開始。有關如何編寫Constraints子類的更多信息,請參閱Constraints中的討論。
performLayout方法應該接受約束並應用它們。佈局算法的輸出是設置在對象上的字段,用於描述父對象佈局的對象幾何圖形。例如,使用RenderBox的輸出是RenderBox.size字段。若是父級指定parentUsesSize爲true,則在調用子級佈局時,此輸出只能由父級讀取。 任什麼時候候渲染對象上的任何變化都會影響該對象的佈局,它應該調用markNeedsLayout。 
Flutter渲染對象樹的根是一個RenderView。這個對象有單獨的子級,它必須是一個RenderBox。所以,若是你想在渲染樹中有一個自定義的RenderObject子類,你有兩種選擇:你可能須要替換RenderView自己,或者你須要一個RenderBox做爲它的子類。 (後者是更常見的狀況。)
它會覆蓋performLayout方法來建立一個適合您的類的Constraints對象,並將其傳遞給該子對象的佈局方法。
渲染對象的佈局應該僅取決於其子佈局的輸出,而且只有在佈局調用中將parentUsesSize設置爲true時纔是如此。此外,若是設置爲true,且要呈現子對象,則父對象必須調用子對象的佈局,不然在子對象更改佈局輸出時不會通知父對象。
能夠設置傳輸附加信息的渲染對象協議。 例如,在RenderBox協議中,您能夠查詢您的子級的固有尺寸和基線幾何。 可是,若是這樣作了,那麼當父級在最後一個佈局階段使用它時,每當附加信息發生變化時,子級都必須在父級上調用markNeedsLayout。 有關如何實現此操做的示例,請參閱RenderBox.markNeedsLayout方法。 它覆蓋了RenderObject.markNeedsLayout,以便若是父節點查詢了內部或基準信息,則每當子節點的幾何結構發生變化時,它都會被標記爲dirty。
 
  轉載請註明出處
From crash_coder linguowu linguowu0622@gamil.com
相關文章
相關標籤/搜索