Flutter 的核心設計思想即是「一切皆 Widget」前端
Widget 是 Flutter 功能的抽象描述,是視圖的配置信息,一樣也是數據的映射,是 Flutter 開發框架中最基本的概念。前端框架中常見的名詞,好比視圖(View)、視圖控制器(View Controller)、活動(Activity)、應用(Application)、佈局(Layout)等,在 Flutter 中都是 Widget。瀏覽器
一般狀況下,都會用到視圖樹(View Tree)的概念。而 Flutter 將視圖樹的概念進行了擴展,把視圖數據的組織和渲染抽象爲三部分,即 Widget,Element 和 RenderObject。前端框架
Widget 是 Flutter 世界裏對視圖的一種結構化描述,你能夠把它看做是前端中的「控件」或「組件」。Widget 是控件實現的基本邏輯單位,裏面存儲的是有關視圖渲染的配置信息,包括佈局、渲染屬性、事件響應信息等。數據結構
Flutter 將 Widget 設計成不可變的,因此當視圖渲染的配置信息發生變化時,Flutter 會選擇重建 Widget 樹的方式進行數據更新,以數據驅動 UI 構建的方式簡單高效。但,這樣作的缺點是,由於涉及到大量對象的銷燬和重建,因此會對垃圾回收形成壓力。框架
不過,Widget 自己並不涉及實際渲染位圖,因此它只是一份輕量級的數據結構,重建的成本很低。另外,因爲 Widget 的不可變性,能夠以較低成本進行渲染節點複用,所以在一個真實的渲染樹中可能存在不一樣的 Widget 對應同一個渲染節點的狀況,這無疑又下降了重建 UI 的成本。ide
Element 是 Widget 的一個實例化對象,它承載了視圖構建的上下文數據,是鏈接結構化的配置信息到完成最終渲染的橋樑。源碼分析
Flutter 渲染過程,能夠分爲這麼三步:佈局
那爲何須要增長中間的這層 Element 樹呢?直接由 Widget 命令 RenderObject 去幹活兒很差嗎?性能
答案是,能夠,但這樣作會極大地增長渲染帶來的性能損耗。ui
由於 Widget 具備不可變性,但 Element 倒是可變的。實際上,Element 樹這一層將 Widget 樹的變化(相似 React 虛擬 DOM diff)作了抽象,能夠只將真正須要修改的部分同步到真實的 RenderObject 樹中,最大程度下降對真實渲染視圖的修改,提升渲染效率,而不是銷燬整個渲染視圖樹重建。
RenderObject 是主要負責實現視圖渲染的對象。
Flutter 經過控件樹(Widget 樹)中的每一個控件(Widget)建立不一樣類型的渲染對象,組成渲染對象樹。
渲染對象樹在 Flutter 的展現過程分爲四個階段,即佈局、繪製、合成和渲染。 其中,佈局和繪製在 RenderObject 中完成,Flutter 採用深度優先機制遍歷渲染對象樹,肯定樹中各個對象的位置和尺寸,並把它們繪製到不一樣的圖層上。繪製完畢後,合成和渲染的工做則交給 Skia 搞定。
abstract class RenderObjectWidget extends Widget {
@override
RenderObjectElement createElement();
@protected
RenderObject createRenderObject(BuildContext context);
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
...
}
複製代碼
RenderObjectWidget 是一個抽象類。這個類同時擁有建立 Element、RenderObject,以及更新 RenderObject 的方法。
但實際上,RenderObjectWidget 自己並不負責這些對象的建立與更新。
對於 Element 的建立,Flutter 會在遍歷 Widget 樹時,調用 createElement 去同步 Widget 自身配置,從而生成對應節點的 Element 對象。而對於 RenderObject 的建立與更新,實際上是在 RenderObjectElement 類中完成的。
abstract class RenderObjectElement extends Element {
RenderObject _renderObject;
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
@override
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
...
}
複製代碼
在 Element 建立完畢後,Flutter 會調用 Element 的 mount 方法。在這個方法裏,會完成與之關聯的 RenderObject 對象的建立,以及與渲染樹的插入工做,插入到渲染樹後的 Element 就能夠顯示到屏幕中了。
若是 Widget 的配置數據發生了改變,那麼持有該 Widget 的 Element 節點也會被標記爲 dirty。在下一個週期的繪製時,Flutter 就會觸發 Element 樹的更新,並使用最新的 Widget 數據更新自身以及關聯的 RenderObject 對象,接下來便會進入 Layout 和 Paint 的流程。而真正的繪製和佈局過程,則徹底交由 RenderObject 完成。
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
...
void layout(Constraints constraints, { bool parentUsesSize = false }) {...}
void paint(PaintingContext context, Offset offset) { }
}
複製代碼
佈局和繪製完成後,接下來的事情就交給 Skia 了。在 VSync 信號同步時直接從渲染樹合成 Bitmap,而後提交給 GPU。
React:JSX->虛擬DOM->瀏覽器DOM
React Native:JSX->虛擬DOM->Android/iOS原生控件
flutter:Widget->Element(相似虛擬DOM,只是一種數據結構)-> RenderObject 交給底層渲染
Element是可複用的,只要Widget先後類型同樣。好比Widget是藍色的,重建後變紅色了,Element是會複用的。因此是多個Widget(銷燬先後)會對應一個Element。而Widget是不可變的,一旦改變就會銷燬重建。