在flutter
中,一切皆Widget
。不管是顯示界面的UI元素,如Text
、Image
、Icon
等;仍是功能性組件,如手勢檢測的GestureDetector
組件、應用主題數據傳遞的Theme
組件、移除系統組件自帶Padding的MediaQuery
組件等。能夠說,flutter
界面就是由一個個粒度很是細的Widget
組合起來的。html
因爲Widget
是不可變的,因此當視圖更新時,flutter
會建立新的Widget
來替換舊的Widget
並將舊的Widget
銷燬。但這樣就會涉及到大量Widget
對象的銷燬和重建,從而對垃圾回收形成壓力。也所以,flutter
將Widget
設計的十分輕量,並將視圖的配置信息與渲染抽象出來,分別交給Element
與RenderObject
。從而使得Widget
只起一個組織者做用,能夠將Element
與RenderObject
組合起來,構成一個視圖。算法
前面說過Widget
是一種很是輕量且不可變的數據結構,只起一個組織者做用。那麼它是如何輕量的尼?下面咱們就從源碼來一窺究竟。markdown
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
/// 建立Widget對應的Element對象,Element對象存儲了Widget的配置信息
@protected
Element createElement();
/// 判斷是否能夠更新Widget
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
}
複製代碼
Widget
是一個抽象類,它只有兩個方法:數據結構
Widget
對應的Element
對象。Widget
是否可更新。根據Widget
的runtimeType
與key
這兩個字段來判斷。因爲Widget
能夠將Element
與RenderObject
組合成一個視圖,但從上面源碼咱們能夠發現,Widget
並無建立RenderObject
對象的方法,那麼它是如何建立RenderObject
對象的尼?實際上是經過RenderObjectWidget
的createRenderObject
方法來建立的,此Widget
是一個很是重要的類,若是不直接或間接繼承該類,Widget
就沒法顯示在界面上。下面咱們對RenderObjectWidget
源碼一窺究竟。框架
abstract class RenderObjectWidget extends Widget {
...
const RenderObjectWidget({ Key key }) : super(key: key);
/// RenderObjectWidget對應着RenderObjectElement及其子類
@override
RenderObjectElement createElement();
/// 建立一個RenderObject對象
@protected
RenderObject createRenderObject(BuildContext context);
/// 更新renderObject
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
/// 將renderObject從render樹中移除
@protected
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
複製代碼
RenderObjectWidget
是一個繼承自Widget
的子類,但它比Widget
多幾個方法。ide
RenderObject
對象,在該對象中會將視圖數據繪製到不一樣的圖層上。筆者認爲它對應着Android中ViewManager的addView方法。Widget
所持有的RenderObject
對象。筆者認爲它對應着Android中ViewManager的updateViewLayout方法。RenderObject
對象從Render樹中移除,也就是銷燬RenderObject
對象。筆者認爲它對應着Android中ViewManager的removeView方法。因爲RenderObject
主要是將視圖繪製成不一樣的圖層,而後再顯示在屏幕上。因此只有當咱們的組件直接或間接繼承自RenderObjectWidget
時,纔會經過RenderObject
來進行繪製、渲染,從而顯示在屏幕上,如RichText
、Row
、Center
等。不然只是一個用來組裝組件的容器,如Text
、ListView
等。函數
Element
是可變的,這裏的可變是指Element
擁有本身的生命週期,能夠根據生命週期來重用或銷燬Element
對象,減小對象的頻繁建立及銷燬。它承載了視圖構建的上下文數據,也是Element
在鏈接Widget
與RenderObject
的橋樑,Element
與Widget
是一對多的關係。因爲Element
是可變的,因此經過Element
將Widget
樹的變化(相似React虛擬DOM diff)作了抽象,能夠將真正須要修改的部分同步到真實的RenderObject
樹中,最大程度下降對真實渲染視圖的修改,提升渲染效率,而不是銷燬整個渲染視圖樹重建。下面咱們來對Element
的源碼一窺究竟。佈局
/// Element對象中存儲的是widget的配置信息
abstract class Element extends DiagnosticableTree implements BuildContext {
...
/// 是一個很是重要的方法,主要是更新子Element的配置信息。
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {...}
/// 將當前Element添加到Element樹中指定的位置,該位置由父級指定
/// 該方法會改變當前Element的狀態,由初始化(initial)狀態改成活動(active)狀態。
@mustCallSuper
void mount(Element parent, dynamic newSlot) {...}
/// 更新當前Element對應的Widget
/// 該方法僅在Element的活動(active)狀態期間調用
@mustCallSuper
void update(covariant Widget newWidget) {...}
/// 改變當前Element在父級中的位置
/// 在MultiChildRenderObjectElement或其餘具備多個子元素的[RenderObjectElement]子類中調用。如:Flex及其子類(Column、Row)、Stack等
@protected
void updateSlotForChild(Element child, dynamic newSlot) {...}
void _updateSlot(dynamic newSlot) {...}
void _updateDepth(int parentDepth) {...}
/// 從render樹中移除當前Element所對應的RenderObject對象
void detachRenderObject() {...}
/// 將RenderObject對象添加到render樹中指定的位置上
void attachRenderObject(dynamic newSlot) {...}
...
/// 初始化Widget,建立Widget對應的Element對象。該方法會調用Widget的createElement方法
/// 該方法一般由updateChild方法調用,但也能夠由須要對建立Element進行更細粒度控制的子類直接調用
@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {...}
...
/// 將指定Element的狀態由活動狀態改成非活動狀態,並從Render樹中移除該Element持有的RenderObject對象
@protected
void deactivateChild(Element child) {...}
/// 從Element的子列表中移除指定的子Element,以準備在Element樹的其餘地方重用子Element
@protected
void forgetChild(Element child);
void _activateWithParent(Element parent, dynamic newSlot) {...}
static void _activateRecursively(Element element) {...}
/// 將Element由初始化狀態改成活動狀態的具體實現,在mount方法中會調用該方法
@mustCallSuper
void activate() {...}
/// 將Element的狀態由活動轉變爲非活動狀態(等待)。處於非活動狀態時,該Element不會被Widget使用,亦不會出如今屏幕上。在當前幀結束以前,該Element會一直存在,若是當前幀結束後,該Element還沒有被使用,則該Element狀態將改變爲銷燬狀態,從而銷燬該Element。
@mustCallSuper
void deactivate() {...}
...
/// 將Element的狀態由非活動(等待)狀態改成銷燬狀態,銷燬當前Element
@mustCallSuper
void unmount() {...}
@override
RenderObject findRenderObject() => renderObject;
//計算Widget的size,size是從RenderObject中獲取的的
@override
Size get size {...}
...
/// 當Element的依賴發生變化時會調用該方法
@mustCallSuper /// 這個是必須調用父類方法的註解吧???
void didChangeDependencies() {...}
...
/// 將Element元素標記爲dirty並添加到全局列表中,以便下次在下一幀中從新構建
void markNeedsBuild() {...}
/// Called by the [BuildOwner] when [BuildOwner.scheduleBuildFor] has been
/// called to mark this element dirty, by [mount] when the element is first
/// built, and by [update] when the widget has changed.
void rebuild() {...}
/// 在進行必定的檢查後調用rebuild()方法
@protected
void performRebuild();
}
複製代碼
Element
源碼裏東西仍是蠻多的,但其實只有如下一些核心的方法。post
Element
,它主要有如下幾種狀況。newWidget == null | newWidget != null | |
---|---|---|
child == null | 返回null | 返回一個新的Element對象 |
child != null | 移除child並返回null | 若是可能就更新child,返回child或者一個新的Element對象。 |
Element
添加到Element
樹中指定的位置,該位置由父級指定。該方法會改變當前Element
的狀態,由初始化(initial)狀態改成活動(active)狀態。Element
對應的Widget
。該方法僅在Element
的活動(active)狀態期間調用。MultiChildRenderObjectElement
或其餘具備多個子元素的RenderObjectElement
子類中調用。如:Flex
及其子類(Column
、Row
)、Stack
等。Element
所對應的RenderObject
對象。RenderObject
對象添加到render樹中指定的位置上。Element
對象。該方法會調用Widget
的createElement
方法來建立Element
對象。該方法一般由updateChild方法調用,但也能夠由須要對建立Element進行更細粒度控制的子類直接調用。Element
的狀態由活動狀態改成非活動狀態,並從render樹中移除該 Element
持有的RenderObject
對象Element
由初始化狀態改成活動狀態的具體實現,在mount
方法中會調用該方法Element
的狀態由活動轉變爲非活動狀態(等待)。處於非活動狀態時,該Element
不會被Widget
使用,亦不會出如今屏幕上。在當前幀結束以前,該Element
會一直存在,若是當前幀結束後,該Element
還沒有被使用,則該Element
狀態將改變爲銷燬狀態,從而銷燬該Element
。Element
的狀態由非活動(等待)狀態改成銷燬狀態,銷燬當前Element
。從上面代碼中,咱們能夠發現Element
在每一幀內都會在如下幾種狀態之間轉換。性能
createElement
方法建立了一個Elmenet
對象。mount
方法將Elmenet
對象添加到了Elmenet
樹中。deactivate
方法將Elmenet
對象從Elmenet
樹中移除。unmount
方法將Elmenet
對象銷燬。Element
的生命週期流程以下:
RenderObject
與Widget
、Element
相比,乾的活是最苦逼的。由於要進行進行視圖的具體渲染,將視圖數據繪製成不一樣層級。若是沒有它,視圖就沒法顯示在屏幕上。下面就從源碼裏一窺究竟。
/// 顧名思義,主要是來繪製界面
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
/// 佈局開始
...
@override
void attach(PipelineOwner owner) {...}
/// 將當前RenderObject對象的佈局信息標記爲dirty
void markNeedsLayout() {...}
/// 將當前RenderObject對象的父對象的佈局信息標記爲dirty
@protected
void markParentNeedsLayout() {...}
/// 該方法裏僅調用了markNeedsLayout與markParentNeedsLayout方法
void markNeedsLayoutForSizedByParentChange() {...}
void _cleanRelayoutBoundary() {...}
void scheduleInitialLayout() {...}
void _layoutWithoutResize() {...}
/// 完成當前渲染對象的佈局,也是界面UI真正開始佈局的地方。對應着Android中View的layout方法
void layout(Constraints constraints, { bool parentUsesSize = false }) {...}
...
/// 僅使用約束更新渲染對象大小。
/// 僅當[sizesByParent]爲true時才調用此函數。
@protected
void performResize();
/// 爲當前Render對象計算佈局大小,不能直接調用,由layout方法調用
@protected
void performLayout();
@protected
void invokeLayoutCallback<T extends Constraints>(LayoutCallback<T> callback) {...}
/// 旋轉當前Render對象(還沒有實現)
void rotate({
int oldAngle, // 0..3
int newAngle, // 0..3
Duration time
}) { }
/// 佈局結束
// 繪製開始
...
/// 將當前Render對象的合成狀態設爲dirty
void markNeedsCompositingBitsUpdate() {...}
...
bool _needsPaint = true;
/// 將當前Render對象標記爲須要從新繪製
void markNeedsPaint() {...}
void _skippedPaintingOnLayer() {...}
void scheduleInitialPaint(ContainerLayer rootLayer) {...}
/// 圖層替換。
void replaceRootLayer(OffsetLayer rootLayer) {...}
void _paintWithContext(PaintingContext context, Offset offset) {...}
...
/// 在該方法中進行真正的繪製,通常由子類重寫,
void paint(PaintingContext context, Offset offset) { }
...
/// 繪製結束
...
複製代碼
別看RenderObject
源碼那麼多。但核心方法其實只有兩個。
Widget
指定其在屏幕上的位置。筆者認爲它對應着Android中View的layout
方法。draw
方法。經過RenderObject
就能夠將一個個Widget
繪製成對應的圖層,因爲圖層每每會很是多,因此直接向GPU傳遞這些圖層數據會很是低效。所以須要在Engine
中將這些圖層進行合併及光柵化,最後在將這些處理後的數據傳遞給GPU。
關於繪製原理的更多知識能夠去YouTube看谷歌推出的講解視頻:Flutter’s renderding pipeline
接下來用一個示例來講明Widget
、Element
及RenderObject
三者之間的關係。
_myWidget() {
return Center(
child: Column(
children: <Widget>[
Text("1111"),
Row(
children: <Widget>[Text("222"), Text("3333")],
),
Icon(Icons.ac_unit)
],
),
);
}
複製代碼
在上面例子中,有一個Center
來讓Widget
居中展現,一個Column
來讓Widget
按照垂直方向排列,一個Row
來讓Widget
按照水平方向排列,多個Text
及Icon
來展現。
flutter
在對上面的Widget
遍歷完成之後,就會建立對應的Widget
樹、Element
樹及RenderObject
樹。以下:
注意:因爲
Text
組件不持有RenderObject
對象,因此render樹中的Text
只是一個泛指。
能夠發現,在上面的三個樹中,Widget
、Element
及RenderObject
是一一對應的,在Element
對象中會同時持有Widget
對象及RenderObject
對象。
固然,Widget
東西很是多,不可能僅憑一篇文章就可以描述清楚的,本文也只是從總體結構上來對Widget
進行一次描述,方便你們深刻的去了解Widget
。
最後來一個思考題,Flutter
的Widget
粒度爲何那麼細?一位阿里大佬給了我一種思路。
因爲Flutter借鑑了React Native的差分算法來更新界面。那麼粒度越細,更新界面的效果就越好。
那麼你們覺得尼???
【參考資料】