深入理解 Widget汁雷,構建 Flutter 界面的基石

(一)什么是 Widget

在官方的架構圖中,Widget 是整個視圖描述的基礎报咳。Widget 是 Flutter 功能的抽象描述侠讯,是視圖的配置信息,同樣也是數(shù)據(jù)的映射暑刃,是 Flutter 開發(fā)框架中最基本的概念厢漩。前端框架中常見的名字,比如視圖(View)岩臣、視圖控制器(View Controller)溜嗜、活動(Activity)、應用(Application)架谎、布局(Layout)等炸宵,在 Flutter 中都是 Widget。

Flutter 架構圖

事實上谷扣,Flutter 的核心設計思想便是“一切皆 Widget”土全。所以,學習 Flutter会涎,首先得從學會使用 Widget 開始涯曲。


(二)Widget 渲染過程

在開發(fā)中,我們往往會關注的一個問題:如何結構化地組織視圖數(shù)據(jù)在塔,提供給渲染引擎幻件,最終完成界面顯示。
通常情況下蛔溃,無一例外绰沥,都會用到視圖樹(View Tree)的概念篱蝇。而 Flutter 將視圖樹的概念進行了擴展,把視圖數(shù)據(jù)的組織和渲染抽象為三部分徽曲,即 Widget零截,Element 和 RenderObject。

三者的關系如下:


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)岂座,通過布局、繪制完成界面的展示杭措。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末费什,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子手素,更是在濱河造成了極大的恐慌鸳址,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刑桑,死亡現(xiàn)場離奇詭異氯质,居然都是意外死亡,警方通過查閱死者的電腦和手機祠斧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門闻察,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人琢锋,你說我怎么就攤上這事辕漂。” “怎么了吴超?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵钉嘹,是天一觀的道長。 經(jīng)常有香客問我鲸阻,道長跋涣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任鸟悴,我火速辦了婚禮陈辱,結果婚禮上,老公的妹妹穿的比我還像新娘细诸。我一直安慰自己沛贪,他們只是感情好,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著利赋,像睡著了一般水评。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上媚送,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天中燥,我揣著相機與錄音,去河邊找鬼塘偎。 笑死褪那,一個胖子當著我的面吹牛,可吹牛的內容都是我干的式塌。 我是一名探鬼主播博敬,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼峰尝!你這毒婦竟也來了偏窝?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤武学,失蹤者是張志新(化名)和其女友劉穎祭往,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體火窒,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡硼补,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了熏矿。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片已骇。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖票编,靈堂內的尸體忽然破棺而出褪储,到底是詐尸還是另有隱情,我是刑警寧澤慧域,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布鲤竹,位于F島的核電站,受9級特大地震影響昔榴,放射性物質發(fā)生泄漏辛藻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一互订、第九天 我趴在偏房一處隱蔽的房頂上張望吱肌。 院中可真熱鬧,春花似錦屁奏、人聲如沸岩榆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽勇边。三九已至,卻和暖如春折联,著一層夾襖步出監(jiān)牢的瞬間粒褒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工诚镰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留奕坟,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓清笨,卻偏偏與公主長得像月杉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子抠艾,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內容