前言: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是用戶界面的一部分,而且是不可變的(immutable)。Widget會被inflate到Element,並由Element管理底層渲染樹。Widget自己沒有可變狀態(全部的字段必須是final)。若是想要把可變狀態與Widget關聯起來,可使用StatefulWidget,StatefulWidget經過使用StatefulWidget.createState方法建立State對象,並將之擴充到Element以及合併到樹中;
給定的Widget能夠被包含在樹中(零次或屢次)。一個給定的Widget能夠放置在樹中屢次,好比:多個TextWidget。每次將一個Widget放入樹中時,它都會被擴充到一個Element中,這也意味着屢次併入樹中的Widget將會被屢次擴充進對應的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來進行對比判斷;算法
Flutter建立Element的可見樹,相對於Widget來講,是可變的,一般的Flutter界面開發中,咱們不用直接操做Element,而是由框架層實現內部邏輯;就如一個UI視圖樹中,可能包含有多個TextWidget(Widget被使用屢次),可是放在內部視圖樹的視角,這些TextWidget都是填充到一個個獨立的Element中;編程
一樣,咱們先來看一下Element這個類中的屬性:框架
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 |
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
RenderObjects有一個父級,並有一個名爲parentData的插槽,其中父級RenderObject能夠存儲特定於子級的數據,例如子級位置。 RenderObject類也實現了基本的佈局和繪製協議。可是,RenderObject類沒有定義子模型(例如,節點是否有零個,一個或多個子節點)。它也沒有定義座標系(例如,子級是否位於笛卡爾座標系,極座標系等)或特定的佈局協議(例如佈局是寬度高度仍是尺寸約束或者父級在子級佈置以前仍是以後設置子級的大小和位置等;或者確實是否容許子級讀取他們父級的parentData插槽)。 RenderBox子類引入佈局系統使用笛卡爾座標。
在大多數狀況下,RenderObject自己的子類化過分,RenderBox將是一個更好的起點。可是,若是渲染對象不想使用笛卡爾座標系,那麼它應該直接從RenderObject繼承。這容許它經過使用約束的新子類而不是使用BoxConstraints來定義本身的佈局協議,而且可能使用全新的一組對象和值來表示輸出的結果而不只僅是一個Size。這種增長的靈活性的代價是沒法依賴RenderBox的功能。例如,RenderBox實現了一個內在的尺寸調整協議,它容許您在沒有徹底鋪設的狀況下測量一個子級,以這樣的方式,若是該子級改變了尺寸,父級將再次佈置(考慮到子級的新尺寸)。這是一個微妙的和容易出錯的功能。編寫RenderBox的大多數方面也適用於編寫RenderObject,所以推薦先閱讀RenderBox的相關討論。主要區別在於佈局和命中測試,由於這些是RenderBox主要專一的方面。
佈局協議從約束的子類開始。有關如何編寫Constraints子類的更多信息,請參閱Constraints中的討論。performLayout方法應該接受約束並應用它們。佈局算法的輸出是設置在對象上的字段,用於描述父對象佈局的對象幾何圖形。例如,使用RenderBox的輸出是RenderBox.size字段。若是父級指定parentUsesSize爲true,則在調用子級佈局時,此輸出只能由父級讀取。 任什麼時候候渲染對象上的任何變化都會影響該對象的佈局,它應該調用markNeedsLayout。
轉載請註明出處From crash_coder linguowu linguowu0622@gamil.com