(一)什么是 Widget
在官方的架構圖中,Widget 是整個視圖描述的基礎报咳。Widget 是 Flutter 功能的抽象描述侠讯,是視圖的配置信息,同樣也是數(shù)據(jù)的映射暑刃,是 Flutter 開發(fā)框架中最基本的概念厢漩。前端框架中常見的名字,比如視圖(View)岩臣、視圖控制器(View Controller)溜嗜、活動(Activity)、應用(Application)架谎、布局(Layout)等炸宵,在 Flutter 中都是 Widget。
事實上谷扣,Flutter 的核心設計思想便是“一切皆 Widget”土全。所以,學習 Flutter会涎,首先得從學會使用 Widget 開始涯曲。
(二)Widget 渲染過程
在開發(fā)中,我們往往會關注的一個問題:如何結構化地組織視圖數(shù)據(jù)在塔,提供給渲染引擎幻件,最終完成界面顯示。
通常情況下蛔溃,無一例外绰沥,都會用到視圖樹(View Tree)的概念篱蝇。而 Flutter 將視圖樹的概念進行了擴展,把視圖數(shù)據(jù)的組織和渲染抽象為三部分徽曲,即 Widget零截,Element 和 RenderObject。
三者的關系如下:
(1)Widget
Widget 是 Flutter 中對視圖的一種結構化描述涧衙,它可以看作是前端中的“控件”或“組件”。Widget 是控件實現(xiàn)的基本邏輯單位奥此,里面存儲的是有關視圖渲染的配置信息弧哎,包括布局、渲染屬性稚虎、時間響應信息等撤嫩。
在頁面渲染上,F(xiàn)lutter 將 Widget 設計成不可變的蠢终,所以當視圖渲染的配置信息發(fā)生變化時序攘,F(xiàn)lutter 會選擇重建 Widget 樹的方式進行數(shù)據(jù)更新,以數(shù)據(jù)驅動 UI 構建的方式簡單高效寻拂。
缺點就是涉及到大量對象的銷毀和重建程奠,所以會對垃圾回收造成壓力。不過祭钉,Widget 本身并不涉及實際渲染位圖瞄沙,所以它只是一份輕量級的數(shù)據(jù)結構,重建的成本很低朴皆。
另外,由于 Widget 的不可變性泛粹,可以以較低成本進行渲染節(jié)點復用遂铡,因此在一個真實的渲染樹種可能存在不同 Widget 對應同一個渲染節(jié)點的情況,這無疑又降低了重建 UI 的成本晶姊。
(2)Element
Element 是 Widget 的一個實例化對象扒接,它承載了視圖構建的上下文數(shù)據(jù),是連接結構化的配置信息到完成最終渲染的橋梁们衙。
Flutter 渲染過程钾怔,可以分為三步:
- 首先,通過 Widget 樹生成對應的 Element 樹蒙挑;
- 然后宗侦,創(chuàng)建相應的 ReaderObject 并關聯(lián)到 Element .renderObject 屬性上;
- 最后忆蚀,構建成 RenderObject 樹矾利,以完成最終的渲染姑裂。
可以看到,Element 同時持有 Widget 和 RenderObject男旗。最終負責渲染工作的只有 RenderObject舶斧。那么,為什么要增加 Element 樹這個中間層呢察皇?而不是由 Widget 直接命令 RenderObject 呢茴厉?
答案是,可以什荣,但是這樣做會極大地增加渲染帶來的性能損耗矾缓。
因為 Widget 具有不可變性,但 Element 卻是可變的溃睹。實際上而账,Element 樹這一層將 Widget 樹的變化(類似 React 虛擬 DOM diff)做了抽象,可以只將真正需要修改的部分同步到真實的 RenderObject 樹種因篇,最大程度降低對真實渲染視圖的修改泞辐,提高渲染效率,而不是銷毀整個渲染視圖樹重建竞滓。
這就是 Element 樹存在的意義咐吼。
(3)RenderObject
RenderObject 主要負責實現(xiàn)視圖渲染的對象。
Flutter 通過控件樹(Widget 樹)中的每個控件(Widget)創(chuàng)建不同類型的渲染對象商佑,組成渲染對象樹锯茄。
而渲染對象樹在 Flutter 的展示過程分為四個階段:布局、繪制茶没、合成和渲染肌幽。其中,布局和繪制在 RenderObject 中完成抓半,F(xiàn)lutter 采用深度優(yōu)先機制遍歷渲染對象樹喂急,確定樹中各個對象的位置和尺寸,并把它們繪制到不同的圖層上笛求。繪制完畢后廊移,合成和渲染的工作則交給 Skia 搞定。
(二)RenderObjectWidget
我們知道探入,在 Flutter 中有 StatelessWidget 和 StatefulWidget 兩種用來組裝控件的容器狡孔,但并不負責組件最后的布局和繪制。在 Flutter 中蜂嗽,布局和繪制工作實際上是在 Widget 的另一個子類 RenderObjectWidget 內完成的苗膝。
通過查看 RenderObjectWidget 的源碼,來分析 Element 和 RenderObject 是如何完成圖形渲染工作的植旧。
abstract class RenderObjectWidget extends Widget {
@override
RenderObjectElement createElement();
@protected
RenderObject createRenderObject(BuildContext context);
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
...
}
RenderObjectWidget 是一個抽象類荚醒。我們通過源碼可以看到芋类,這個類中同時擁有創(chuàng)建 Element、RenderObject界阁,以及更新 RenderObject 的方法侯繁。
但實際上,RenderObjectWidget 本身并不負責這些對象的創(chuàng)建和更新泡躯。
對于 Element 的創(chuàng)建贮竟,F(xiàn)lutter 會在遍歷 Widget 樹時,調用 creatElement 去同步 Widget 自身配置较剃,從而生成對應節(jié)點的 Element 對象咕别。而對于 RenderObject 的創(chuàng)建與更新,其實是在 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 創(chuàng)建完畢后惰拱,F(xiàn)lutter 會調用 Element 的 mount 方法妥凳。在這個方法中咒循,會完成與之關聯(lián)的 RenderObject 對象的創(chuàng)建,以及與渲染樹的插入工作凯旋,插入到渲染樹后的 Element 就可以顯示到屏幕中馋没。
如果 Widget 的配置數(shù)據(jù)發(fā)生變化昔逗,那么持有該 Widget 的 Element 節(jié)點也會被標記為 dirty。在下一個周期的繪制時篷朵,F(xiàn)lutter 就會觸發(fā) Element 樹的更新勾怒,并使用最新的 Widget 數(shù)據(jù)更新自身以及關聯(lián)的 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。
(三)案例
根據(jù)下面的案例缚够,了解說明 Widget幔妨、Element 與 RenderObject 在渲染過程中的關系鹦赎。
一個 Row 容器放置了 4 個子 Widget谍椅,左邊是 Image,右邊是一個 Column 容器下排布的兩個 Text古话。
那么雏吭,在 Flutter 遍歷完 Widget 樹,創(chuàng)建了各個子 Widget 對應的 Element 的同時陪踩,也創(chuàng)建了與之關聯(lián)的杖们、負責實際布局和繪制的 RenderObject悉抵。
總結
主要學習了 Flutter 中視圖數(shù)據(jù)的組織和渲染抽象的三個核心概念:Widget、Element 和 RenderObject摘完。
Widget 是 Flutter 世界里對視圖的一種結構化描述姥饰,里面儲存的是有關視圖渲染的配置信息;
Element 是 Widget 的一個實例化對象孝治,講 Widget 樹的變化做了抽象列粪,能夠做到只將真正需要修改的部分同步到真是的 RenderObject 樹中,最大程度地優(yōu)化了從結構化的配置信息到完成最終渲染的過程谈飒;
RenderObject 是負責實現(xiàn)視圖的最終呈現(xiàn)岂座,通過布局、繪制完成界面的展示杭措。