Flutter內(nèi)部原理

Flutter在內(nèi)部實際上是如何工作的?

什么是Widget陋气、 Element、 BuildContext、 RenderOject匠童、 Binding…

難度: 初學(xué)者

簡介

去年,當(dāng)我開始進入傳說中的Flutter世界時塑顺,與今天相比汤求,當(dāng)時在互聯(lián)網(wǎng)上幾乎找不到資料俏险。 現(xiàn)在盡管撰寫了許多文章,但很少有人談?wù)揊lutter的實際工作原理扬绪。

Widget竖独、Element、BuildContext終究是什么挤牛? 為什么Flutter運行流暢莹痢,為什么有時運行效果與預(yù)期不同? 什么是樹墓赴?

在編寫應(yīng)用程序時竞膳,在95%的情況下,只與Widget打交道诫硕,顯示某些內(nèi)容或與屏幕進行交互坦辟。 但是你是否從未想過所有這些神奇之處實際上是如何運行的? 系統(tǒng)如何知道何時更新屏幕章办,以及需要更新哪些部分锉走?


第一部分1:背景

這篇文章的第一部分介紹了一些關(guān)鍵概念,這些概念是用來更好的理解這個帖子的第二部分藕届。


回到設(shè)備上來

這一次挪蹭,讓我們從頭開始,回到基礎(chǔ)休偶。

當(dāng)您查看設(shè)備時梁厉,或更具體地說,查看設(shè)備上運行的應(yīng)用程序時椅贱,只會看到一個屏幕懂算。

實際上,您所看到的只是一系列像素庇麦,這些像素共同構(gòu)成一個平面圖像(2維)计技,并且當(dāng)您用手指觸摸屏幕時,設(shè)備只能識別手指在玻璃板上的位置山橄。

該應(yīng)用程序的所有神奇之處(從視覺角度而言)垮媒,在于使平面圖像在大多數(shù)情況下基于與以下對象的交互來更新:

  • 設(shè)備屏幕(例如屏幕上的手指)
  • 網(wǎng)絡(luò)(例如與服務(wù)器通信)
  • 時間(例如動畫)
  • 其他外部傳感器

硬件顯示設(shè)備)可確保在屏幕上渲染圖像,該硬件會定期(通常每秒60次)刷新顯示航棱。此刷新頻率也稱為“ 刷新頻率”睡雇,以Hz(赫茲)表示。

顯示設(shè)備GPU圖形處理單元)接收要在屏幕上顯示的信息饮醇,該圖形處理器是一種專用電子電路它抱,經(jīng)過優(yōu)化和設(shè)計可從某些數(shù)據(jù)(多邊形和紋理)快速生成圖像。 GPU每秒能夠生成要顯示的“圖像”(= 幀緩沖)并將其發(fā)送到硬件的次數(shù)稱為“幀率”朴艰。這是用fps單位測量的(例如每秒60幀或60 fps)观蓄。

您可能會問我混移,為什么我從GPU /硬件和物理傳感器等等渲染的二維平面圖像的概念開始,以及侮穿,它與通常的Flutter Widget有什么關(guān)系歌径?

僅僅因為Flutter應(yīng)用程序的主要目標(biāo)之一,就是合成二維平面圖像并與之交互亲茅,所以我認(rèn)為如果我們從這個角度來看Flutter的工作原理回铛,可能會更容易理解。

…而且因為在Flutter中克锣,信不信由你茵肃,幾乎所有事情都是由必須在正確的時機刷新屏幕的需求所驅(qū)動的!


代碼和物理設(shè)備之間的接口

總有一天娶耍,對Flutter感興趣的每個人都已經(jīng)看到了以下圖片免姿,描述了Flutter的高層次的體系結(jié)構(gòu)。

Flutter Architecture (c) Flutter

當(dāng)使用Dart編寫Flutter應(yīng)用程序時榕酒,我們是在Flutter框架的級別(綠色)。

Flutter框架通過名為Window的抽象層與 Flutter引擎(藍(lán)色)交互故俐。 此抽象層公開了一系列API想鹰,可與設(shè)備間接通信。

Flutter引擎也是通過此抽象層以如下方式通知Flutter框架的:

  • 感興趣的事件發(fā)生在設(shè)備級別(方向更改药版,設(shè)置更改辑舷,內(nèi)存問題,應(yīng)用程序運行狀態(tài)…)
  • 一些事件發(fā)生在屏幕上(=手勢)
  • 平臺通道發(fā)送一些數(shù)據(jù)
  • 而且主要是在Flutter引擎準(zhǔn)備渲染新幀

Flutter框架由Flutter引擎幀渲染驅(qū)動

這種說法很難讓人相信槽片,但這是事實何缓。

除了某些情況(請參閱下文),沒有由Flutter引擎幀渲染觸發(fā)的情況下还栓,F(xiàn)lutter框架代碼不會被執(zhí)行碌廓。

這些例外是:

  • 手勢(=屏幕上的事件)
  • 平臺消息(=設(shè)備發(fā)出的消息,例如GPS)
  • 設(shè)備消息(=表示設(shè)備狀態(tài)變化的消息剩盒,例如方向谷婆,發(fā)送到后臺的應(yīng)用程序,內(nèi)存警告辽聊,設(shè)備設(shè)置…)
  • Futurehttp響應(yīng)

在Flutter引擎幀渲染未要求的情況下纪挎,F(xiàn)lutter框架不會實施任何視覺更改。

(但是跟匆,在我們看來异袄,可以不受Flutter引擎的邀請而進行視覺更改,但這確實是不建議

但是您會問我玛臂,是否執(zhí)行了與gesture(手勢)相關(guān)的某些代碼并導(dǎo)致視覺變化發(fā)生烤蜕,或者我是否正在使用timer(計時器)節(jié)奏執(zhí)行某些任務(wù)而導(dǎo)致視覺變化(例如動畫埠帕,舉個例子),這該如何運行呢玖绿?

如果您希望進行視覺更改敛瓷,或者希望基于計時器執(zhí)行某些代碼,則需要告訴Flutter引擎斑匪,需要渲染某些內(nèi)容呐籽。

通常,在下一次刷新時蚀瘸,F(xiàn)lutter引擎然后會請求Flutter框架運行一些代碼狡蝶,并最終提供要渲染的新場景。

因此贮勃,最大的問題是Flutter引擎如何基于渲染來協(xié)調(diào)整個應(yīng)用程序的行為贪惹?

為了讓您了解內(nèi)部機制,請看以下動畫…

簡短說明(更多細(xì)節(jié)將在后面):

  • 某些外部事件(手勢寂嘉,http響應(yīng)等)甚至future可能會啟動一些任務(wù)奏瞬,導(dǎo)致必須更新渲染。消息被發(fā)送到Flutter引擎以通知它(= Schedule Frame
  • 當(dāng)Flutter引擎準(zhǔn)備好進行渲染更新時泉孩,它會發(fā)出一個Begin Frame請求
  • Begin Frame請求被Flutter框架攔截硼端,后者運行主要與Ticker相關(guān)的??任何任務(wù)(例如動畫等)
  • 這些任務(wù)可能會重新發(fā)出請求以供后面渲染幀…(例如:動畫為完成并且要繼續(xù)進行,它需要稍后再接收另一個“開始幀”
  • 然后寓搬,Flutter引擎發(fā)出一個Draw Frame珍昨。
  • Draw FrameFlutter框架攔截,后者將查找與更新布局有關(guān)的所有任務(wù)句喷,這些任務(wù)與更新布局結(jié)構(gòu)和大小相關(guān)聯(lián)镣典。
  • 完成所有這些任務(wù)后,將繼續(xù)執(zhí)行與在繪制方面的布局更新的有關(guān)任務(wù)唾琼。
  • 如果要在屏幕上繪制一些東西兄春,則它將新的Scene渲染到Flutter引擎中,后者將更新屏幕父叙。
  • 然后神郊,Flutter框架執(zhí)行渲染完成后要運行的所有任務(wù)(= PostFrame回調(diào))以及與渲染無關(guān)的其他后續(xù)任務(wù)。
    *…趾唱,此流程一次又一次地開始涌乳。

RenderView和RenderObject

在深入探討與動作流程相關(guān)的細(xì)節(jié)之前,是時候介紹Rendering Tree這個概念了甜癞。

如前所述夕晓,最終所有東西最終變成了要在屏幕上顯示的一系列像素,并且Flutter框架將我們正在使用應(yīng)用程序開發(fā)的Widget悠咱,轉(zhuǎn)換為將渲染在屏幕上的可視部分蒸辆。

屏幕上渲染的這些可視部分與稱為RenderObject的對象相對應(yīng)征炼,這些對象用于:

  • 根據(jù)尺寸,位置躬贡,幾何形狀以及“渲染內(nèi)容”定義屏幕的某些區(qū)域
  • 識別可能受到手勢影響的屏幕區(qū)域(= 手指

所有RenderObject的集合形成一棵樹谆奥,稱為Render Tree。在該樹的頂部(= root)拂玻,我們找到一個RenderView酸些。

RenderView代表Render Tree的總輸出界面,它本身是RenderObject的特殊版本檐蚜。

從視覺上講魄懂,我們可以將所有這些表示如下:

RenderView - RenderObject

Widgets和RenderObject之間的關(guān)系將在本文后面討論。

現(xiàn)在該深入一點了……


重要的事優(yōu)先 - 綁定的初始化

啟動Flutter應(yīng)用程序時闯第,系統(tǒng)將調(diào)用main()方法市栗,該方法最終將調(diào)用runApp(Widget app)方法。

在對runApp()方法的調(diào)用期間咳短,F(xiàn)lutter框架初始化Flutter框架Flutter引擎之間的接口填帽。 這些接口稱為 綁定

綁定 - 簡介

綁定Flutter引擎Flutter框架之間的某種膠水诲泌。只有通過這些綁定盲赊,才能在Flutter的兩個部分(引擎和框架)之間交換數(shù)據(jù)。
(此規(guī)則只有一個例外:RenderView敷扫,但我們稍后會看到)

每個綁定負(fù)責(zé)處理一組特定的任務(wù),動作诚卸,事件葵第,并按活動領(lǐng)域重新分組。

在撰寫本文時合溺,Flutter框架包含8個綁定卒密。

下面是本文將要討論的4個:

  • SchedulerBinding
  • GestureBinding
  • RendererBinding
  • WidgetsBinding

為了完整起見,最后4個(本文將不介紹):

  • ServicesBinding:負(fù)責(zé)處理平臺通道發(fā)送的消息
  • PaintingBinding:負(fù)責(zé)處理圖像緩存
  • SemanticsBinding:保留供以后實現(xiàn)與Semantics相關(guān)的??所有內(nèi)容
  • TestWidgetsFlutterBinding:由widget測試庫使用

我還可以提到WidgetsFlutterBinding棠赛,但后者實際上不是綁定哮奇,而是某種“ 綁定初始化器 ”。

下圖顯示了我將在本文稍后介紹的綁定與Flutter引擎之間的交互睛约。

Bindings interactions

讓我們看一下這些“主要”綁定鼎俘。


SchedulerBinding

這個綁定具有2個主要職責(zé):

  • 第一個是告訴 Flutter引擎:“嘿!下次您不忙時辩涝,叫醒我贸伐,以便我可以運行一下,并告訴您要渲染的內(nèi)容或是否你需要稍后再呼叫我……”怔揩;
  • 第二個是監(jiān)聽并響應(yīng)此類“喚醒呼叫”(請參閱??稍后的內(nèi)容)

SchedulerBinding何時請求喚醒呼叫捉邢?

  • Ticker需要滴答
    例如脯丝,假設(shè)您有一個動畫,然后啟動它伏伐。動畫由Ticker進行節(jié)奏控制宠进,并以固定間隔(=滴答)被調(diào)用以運行回調(diào)。要運行這樣的回調(diào)藐翎,我們需要告訴Flutter引擎在下次刷新時喚醒我們(= Begin Frame)材蹬。這將調(diào)用ticker回調(diào)來執(zhí)行其任務(wù)。在該任務(wù)結(jié)束時阱高,如果ticker仍需要向前移動赚导,它將調(diào)用 SchedulerBinding安排另一個幀。

  • 更改布局時
    例如赤惊,當(dāng)您響應(yīng)導(dǎo)致視覺變化的事件(例如吼旧,更新屏幕的一部分的顏色,滾動未舟,向屏幕中添加某些東西/從屏幕中刪除某些東西)時圈暗,我們需要采取必要的步驟,最終在屏幕上渲染它裕膀。在這種情況下员串,當(dāng)發(fā)生此類更改時,Flutter框架將調(diào)用SchedulerBinding昼扛,使用Flutter引擎安排另一個幀寸齐。 (我們將在后面看到它的實際運行方式)


GestureBinding

這個綁定以“手指”(= 手勢)來表示與引擎的交互。

特別是抄谐,它負(fù)責(zé)接受與手指有關(guān)的數(shù)據(jù)并確定屏幕的哪些部分受到手勢的影響渺鹦。 然后,它會相應(yīng)地通知此/這些部分蛹含。


RendererBinding

這個綁定是* Flutter引擎和 Render Tree之間的粘合劑毅厚。 它有2個不同的職責(zé):

  • 第一個是監(jiān)聽引擎發(fā)出的事件,告知用戶通過設(shè)備設(shè)置實施的更改浦箱,這些更改會影響視覺效果和/或語義
  • 第二個是為引擎提供要應(yīng)用于顯示的修改吸耿。

為了提供要在屏幕上渲染的修改,此Binding負(fù)責(zé)驅(qū)動PipelineOwner并初始化RenderView酷窥。

PipelineOwner是一種協(xié)調(diào)器咽安,它知道哪些RenderObject需要做一些與布局有關(guān)的事情并協(xié)調(diào)這些動作。


WidgetsBinding

這個綁定可以監(jiān)聽用戶通過設(shè)備設(shè)置實施的更改竖幔,這些更改會影響語言(= 區(qū)域設(shè)置)和語義板乙。

旁注

在稍后的階段,我想所有與Semantic相關(guān)的事件都將遷移到SemanticsBinding,但是在撰寫本文時募逞,情況還不是這樣蛋铆。

除此之外,WidgetsBinding是Widget與Flutter引擎之間的膠水放接。 它有2個不同的主要職責(zé):

  • 第一個主要是驅(qū)動負(fù)責(zé)處理Widget結(jié)構(gòu)更改的過程
  • 第二個是觸發(fā)渲染

Widgets結(jié)構(gòu)更改的處理是通過BuildOwner完成的刺啦。

BuildOwner跟蹤哪些Widget需要重建,并處理應(yīng)用于整個Widget結(jié)構(gòu)的其他任務(wù)纠脾。


第二部分:從Widget到像素

既然我們已經(jīng)介紹了內(nèi)部機制的基礎(chǔ)玛瘸。

在所有的Flutter 文檔中,你可能讀過一切皆Widget苟蹈。

好吧糊渊,這幾乎是正確的,但是為了更加精確慧脱,我寧愿說:

開發(fā)人員的角度來看渺绒,與用戶界面有關(guān)的所有布局和交互都是通過小部件完成的。

為什么要這么精確菱鸥? 因為Widget允許開發(fā)人員根據(jù)尺寸宗兼、內(nèi)容、布局和交互性來定義屏幕的一部分氮采,還有更多戈锻。 那么滴肿,什么是Widget呢拓哟?


不可變的配置

閱讀Flutter源代碼時浙滤,您會注意到Widget類的以下定義。

@immutable
abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });

  final Key key;

  ...
}

這是什么意思呢躯概?

注釋“@immutable”非常重要镰烧,它告訴我們Widget類中的任何變量都必須為FINAL,換句話說:“已定義并分配 只此一次”楞陷。 因此,一旦實例化茉唉,Widget將不再能夠調(diào)整其內(nèi)部變量固蛾。

Widget是一種常量配置,因為它是IMMUTABLE


Widget層次結(jié)構(gòu)

當(dāng)您使用Flutter開發(fā)時度陆,您可以使用Widget來定義屏幕的結(jié)構(gòu)艾凯,例如:

Widget build(BuildContext context){
    return SafeArea(
        child: Scaffold(
            appBar: AppBar(
                title: Text('My title'),
            ),
            body: Container(
                child: Center(
                    child: Text('Centered Text'),
                ),
            ),
        ),
    );
}

該示例使用7個Widget,它們共同構(gòu)成一個層次結(jié)構(gòu)懂傀。 基于代碼的非常簡化結(jié)構(gòu)如下:

Simplified Widgets Tree

如您所見趾诗,這看起來像一棵樹,其中SafeArea是樹的根。


樹后面的森林

如您所知恃泪,Widget本身可能是其他Widget的集合郑兴。 例如,我可以通過以下方式編寫先前的代碼:

Widget build(BuildContext context){
    return MyOwnWidget();
}

這里設(shè)想Widget“ MyOwnWidget”本身會渲染SafeArea贝乎,Scaffold ...情连,但是此示例最重要的是

Widget可能是葉子,樹中的節(jié)點览效,甚至是樹本身却舀,或者為什么不是樹的森林……


樹中的Element概念

我為什么要提這個呢?

正如我們將在后面看到的那樣锤灿,為了能夠生成組成要在設(shè)備上渲染的圖像的像素挽拔,Flutter需要詳細(xì)了解組成屏幕的所有小部分并確定所有部分 ,它將要求膨脹(inflate)所有Widget但校。

為了說明這一點螃诅,請考慮俄羅斯玩偶的原理:關(guān)閉時,您只能看到1個玩偶始腾,但后者包含另一個州刽,然后依次包含另一個,依此類推...

Russian dolls

當(dāng)Flutter將所有widget膨脹到屏幕的一部分時浪箭,這類似于獲取所有不同的俄羅斯玩偶穗椅,即全部的一部分。

下圖顯示了最終Widget層次結(jié)構(gòu)的一部分奶栖,與先前的代碼相對應(yīng)匹表。 用黃色突出顯示了代碼中提到的Widget,以便您可以在生成的局部Widget樹中發(fā)現(xiàn)它們宣鄙。

Inflated Widgets

重要說明

“Widget樹”的用語僅是為了使程序員易于理解袍镀,因為程序員正在使用小部件,但是在Flutter中沒有Widget樹冻晤!

實際上苇羡,正確地說,我們應(yīng)該說:“Element樹

現(xiàn)在是時候介紹Element的概念了……

每個小部件對應(yīng)一個元素鼻弧。 元素彼此鏈接并形成一棵樹设江。 因此,元素是樹中某物的引用攘轩。

首先叉存,將元素視為有父節(jié)點并可能子節(jié)點的節(jié)點。 通過父節(jié)點關(guān)系鏈接在一起度帮,我們得到一個樹形結(jié)構(gòu)歼捏。

An Element

如上圖所示,Element指向一個Widget,并且可能也指向一個RenderObject瞳秽。

甚至更好…Element指向創(chuàng)建元素的Widget瓣履!

讓我回顧一下...

  • 沒有Widget樹,而是Element樹
  • Element是由Widget創(chuàng)建的
  • Element引用創(chuàng)建它的Widget
  • Element與父節(jié)點關(guān)系鏈接在一起
  • Element可以有一個子節(jié)點或多個子節(jié)點
  • Element也可以指向RenderObject

Element定義視覺部件之間如何鏈接

為了更好地可視化元素的概念適合的位置寂诱,我們考慮以下視覺表示:

如您所見拂苹,Element樹WidgetRenderObject之間的實際鏈接。

但是痰洒,為什么Widget會創(chuàng)建Element瓢棒?


三個主要Widget類別

Flutter中,Widget分為3個主要類別丘喻,我個人稱這些類別為(但是這是我將他們分類的方式):

  • 代理

    這些Widget的主要作用是保存一些信息脯宿,這些信息需要供Widget使用,Widget是樹結(jié)構(gòu)的一部分泉粉,以代理為根连霉。此類Widget的典型示例是InheritedWidgetLayoutId

    這些Widget并不直接屬于用戶界面嗡靡,而是被其他用來獲取其可以提供的信息跺撼。

  • 渲染器

    這些Widget與它們定義(或用于推斷)的屏幕布局有直接的關(guān)聯(lián):

    • 尺寸;
    • 位置讨彼;
    • 布局歉井,渲染。

    典型示例包括: Row, Column, Stack哈误, 還有Padding, Align, Opacity, RawImage

  • 組件哩至。

    這些是其他Widget,它們不直接提供與尺寸蜜自,位置菩貌,外觀有關(guān)的最終信息,而是將用于獲取最終信息的數(shù)據(jù)(或提示)重荠。這些Widget通常被稱為組件箭阶。

    例如:RaisedButton, Scaffold, Text, GestureDetector, Container

以下PDF列出了大多數(shù)Widget,按類別重新分組戈鲁。

為什么拆分很重要尾膊? 因為根據(jù)Widget類別,關(guān)聯(lián)了一個對應(yīng)的Element類型...


Element類型

一下是不同的Element類型

如您在上圖中所見荞彼,元素分為兩種主要類型:

  • ComponentElement

    這些元素不直接對應(yīng)于任何視覺渲染部分。

  • RenderObjectElement

    這些元素對應(yīng)于渲染屏幕的一部分待笑。

非常好鸣皂! 到目前為止,有很多信息,但是如何將所有內(nèi)容鏈接在一起寞缝?為什么要引入所有這些癌压?


Widget和Element如何一起運行?

Flutter中荆陆,整個機制依賴于使ElementrenderObject無效(invalidate)滩届。

可以通過不同方式使Element失效:

  • 通過使用setState,這會使整個StatefulElement無效(請注意被啼,我故意不說StatefulWidget
  • 通過通知帜消,由其他proxyElement處理(例如InheritedWidget),這會使依賴于proxyElement的任何Element無效

無效的結(jié)果是在臟Element列表中引用了相應(yīng)的Element浓体。

使renderObject無效意味著未對Element的結(jié)構(gòu)進行任何更改泡挺,而是在renderObject級別上進行了修改,例如

  • 更改其尺寸命浴,位置娄猫,幾何形狀...
  • 需要重新繪制,例如生闲,當(dāng)您僅更改背景顏色媳溺,字體樣式時…

這種無效的結(jié)果是在需要重建或重新繪制的renderObject列表中引用了相應(yīng)的renderObject

無論哪種失效類型碍讯,發(fā)生這種情況時悬蔽,都要求SchedulerBinding(還記得嗎?)要求Flutter引擎安排新的冲茸。

當(dāng)Flutter引擎喚醒SchedulerBinding時屯阀,所有的神奇之處都會發(fā)生……

onDrawFrame()

在本文的前面,我們提到SchedulerBinding具有2個主要職責(zé)轴术,其中之一是準(zhǔn)備處理Flutter引擎發(fā)出的與幀重建有關(guān)的請求难衰。 現(xiàn)在是專注于此的絕佳時機……

下面的部分序列圖顯示了當(dāng)SchedulerBindingFlutter引擎接收到onDrawFrame()請求時會發(fā)生什么情況。

第一步:Element

WidgetsBinding被調(diào)用逗栽,后者首先考慮與Element相關(guān)的??更改盖袭。

由于BuildOwner負(fù)責(zé)處理Element樹,因此WidgetsBinding會調(diào)用buildOwnerbuildScope方法彼宠。

此方法迭代無效元素(=臟)的列表鳄虱,并要求它們進行重建

rebuild()方法的主要原理是:

  1. 請求元素進行rebuild()凭峡,這導(dǎo)致大多數(shù)情況拙已,調(diào)用了該element所引用的Widget的build方法(=方法Widget build(BuildContext context){…})。這個* build()*方法返回一個新的Widget摧冀。
  2. 如果元素沒有子元素倍踪,則將新Widget膨脹(見下文)系宫,否則
  3. 將新Widget與該Element的子Element引用的Widget進行比較。
    如果可以交換(= 相同的Widget類型和鍵)建车,則進行更新扩借,并保留子Element。
    如果無法交換它們缤至,則子Element將被卸載(? 被丟棄
    )潮罪,并且新的Widget會膨脹。
  4. Widget的膨脹導(dǎo)致創(chuàng)建一個新Element领斥,該Element將作為該Element的新Element掛載嫉到。 (掛載 =已插入到Element樹中)

下面的動畫試圖使這種解釋更加直觀。

onDrawFrame() - Elements

Widget膨脹的注意點

當(dāng)Widget膨脹戒突,要求創(chuàng)建一個由widget類別定義的特定類型的新element屯碴。

所以,

  • 一個InheritedWidget將產(chǎn)生一個InheritedElement
  • 一個StatefulWidget將生成一個StatefulElement
  • 一個StatelessWidget將生成一個StatelessElement
  • 一個InheritedModel將生成一個InheritedModelElement
  • 一個InheritedNotifier將生成一個InheritedNotifierElement
  • 一個LeafRenderObjectWidget將生成一個LeafRenderObjectElement
  • 一個SingleChildRenderObjectWidget將生成一個SingleChildRenderObjectElement
  • 一個MultiChildRenderObjectWidget將生成一個MultiChildRenderObjectElement
  • 一個ParentDataWidget將生成一個ParentDataElement

每種element類型都有不同的行為。

例如

  • StatefulElement將在初始化時調(diào)用widget.createState()方法膊存,這將創(chuàng)建State并將其鏈接到Element
  • 一個RenderObjectElement類型將創(chuàng)建一個RenderObject导而,當(dāng)Element被掛載時,這個renderObject將被添加到render樹并鏈接到element隔崎。

第二步:renderObject

一旦完成與dirty元素相關(guān)的所有操作今艺,element樹現(xiàn)在就穩(wěn)定了,現(xiàn)在是時候考慮渲染過程了爵卒。

由于RendererBinding負(fù)責(zé)處理渲染樹虚缎,因此WidgetsBinding調(diào)用RendererBindingdrawFrame方法。

下面的局部圖顯示了在drawFrame()請求期間執(zhí)行的操作順序钓株。

在此步驟中实牡,執(zhí)行以下活動:

  • 要求每個標(biāo)有dirtyrenderObject進行布局(意味著計算其尺寸和幾何形狀)
  • 使用renderObject的圖層對每個標(biāo)記為“需要繪制”的renderObject進行重新繪制。
  • 生成的scene被構(gòu)建并發(fā)送到Flutter引擎轴合,以便后者將其發(fā)送到設(shè)備屏幕创坞。
  • 最后語義也被更新并發(fā)送到Flutter引擎

在該操作流程結(jié)束時,將更新設(shè)備屏幕受葛。


第三步:手勢處理

手勢(=與屏幕上的手指有關(guān)的事件)由GestureBinding處理题涨。

當(dāng)Flutter引擎通過window.onPointerDataPacket API發(fā)送與手勢相關(guān)的事件的信息時,GestureBinding會攔截它总滩,并進行一些緩沖纲堵,并:

  1. 轉(zhuǎn)換Flutter引擎發(fā)出的坐標(biāo),以匹配設(shè)備像素比率闰渔,然后
  2. 請求renderView提供所有RenderObject的列表席函,該列表覆蓋了包含事件坐標(biāo)的屏幕的一部分
  3. 然后迭代該renderObject列表,并將相關(guān)事件分派給它們中的每一個冈涧。
  4. 當(dāng)renderObject正在等待此類事件時向挖,它將對其進行處理蝌以。

從這個解釋中,我們直接看到renderObject有多重要...


第四步:動畫

本文的最后一部分重點關(guān)注動畫的概念何之,更具體地說是Ticker的概念。

啟動動畫時咽筋,通常使用 AnimationController或任何類似的Widget或組件溶推。

Flutter中,與動畫相關(guān)的所有內(nèi)容均指Ticker的概念奸攻。

激活時蒜危,Ticker只做一件事:“ 它請求 SchedulerBinding注冊回調(diào),并要求Flutter引擎在下一個可用回調(diào)時將其喚醒”睹耐。

當(dāng)Flutter引擎準(zhǔn)備就緒時辐赞,它會通過以下請求調(diào)用SchedulerBinding:“ onBeginFrame”。

然后硝训,SchedulerBinding攔截此請求响委,然后遍歷ticker回調(diào)的列表并調(diào)用它們中的每一個。

每個ticker滴答都會被對此事件感興趣的任何控制器攔截以對其進行處理窖梁。如果動畫已完成赘风,則ticker為“disabled”,否則代碼為“ SchedulerBinding”請求安排另一個回調(diào)纵刘。等等…


全局圖

現(xiàn)在我們已經(jīng)了解了Flutter內(nèi)部的工作原理邀窃,這是全局圖:

Internals Big Picture

BuildContext

最后的話...

如果您回想起顯示不同element類型的圖,您很可能會注意到基礎(chǔ)Element的簽名:

abstract class Element extends DiagnosticableTree implements BuildContext {
    ...
}

這就是著名的 BuildContext.

什么是BuildContext呢?

BuildContext是一個接口假哎,它定義了可以由Element以協(xié)調(diào)的方式實現(xiàn)的一系列getter方法瞬捕。

特別是,BuildContext主要用于StatelessWidgetStatefulWidgetbuild()方法或StatefulWidgetState對象中舵抹。

BuildContext不是別的肪虎,只是元素本身對應(yīng)的

  • 重建的Widget(在build或builder方法內(nèi)部)
  • StatefulWidget鏈接到引用上下文變量的State

這意味著大多數(shù)開發(fā)人員甚至在不知情的情況下也不斷地處理 element掏父。

BuildContext有用嗎笋轨?

由于BuildContext既與widget相關(guān)的?? element也與樹中widget的位置相對應(yīng),因此BuildContext對以下情況非常有用:

  • 獲取與widget對應(yīng)的RenderObject的引用(或者如果widget不是renderer赊淑,則*子孫widget
  • 獲取RenderObject的大小
  • 訪問樹爵政。實際上所有通常實現(xiàn)of方法的widget都使用此方法(例如MediaQuery.of(context), Theme.of(context)…

只是為了好玩……

現(xiàn)在我們已經(jīng)了解到BuildContextelement,出于樂趣陶缺,我想向您展示另一種使用它的方式...

以下無用代碼使StatelessWidget可以自我更新(就像它是StatefulWidget钾挟,但不使用任何setState()),通過使用BuildContext...

void main(){
    runApp(MaterialApp(home: TestPage(),));
}

class TestPage extends StatelessWidget {
    // final because a Widget is immutable (remember?)
    final bag = {"first": true};

    @override
    Widget build(BuildContext context){
        return Scaffold(
            appBar: AppBar(title: Text('Stateless ??')),
            body: Container(
                child: Center(
                    child: GestureDetector(
                        child: Container(
                            width: 50.0,
                            height: 50.0,
                            color: bag["first"] ? Colors.red : Colors.blue,
                        ),
                        onTap: (){
                            bag["first"] = !bag["first"];
                            //
                            // This is the trick
                            //
                            (context as Element).markNeedsBuild();
                        }
                    ),
                ),
            ),
        );
    }
}

在我們之間饱岸,當(dāng)您調(diào)用setState()方法時掺出,后者最終會做同樣的事情:_element.markNeedsBuild().


結(jié)論

又是一篇很長的文章徽千,不是嗎

我想這是很有趣的,知道Flutter是如何構(gòu)建的汤锨,并提醒所有東西都被設(shè)計為高效双抽、可擴展且對將來的擴展開放。

此外闲礼,諸如Widget牍汹,ElementBuildContext柬泽, RenderObject之類的關(guān)鍵概念并不總是顯而易見的慎菲。

我希望本文可能有用。

敬請期待新文章锨并。 同時露该,祝您編碼愉快。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末第煮,一起剝皮案震驚了整個濱河市解幼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌空盼,老刑警劉巖书幕,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異揽趾,居然都是意外死亡台汇,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門篱瞎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苟呐,“玉大人,你說我怎么就攤上這事俐筋∏K兀” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵澄者,是天一觀的道長笆呆。 經(jīng)常有香客問我,道長粱挡,這世上最難降的妖魔是什么赠幕? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮询筏,結(jié)果婚禮上榕堰,老公的妹妹穿的比我還像新娘。我一直安慰自己嫌套,他們只是感情好逆屡,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布圾旨。 她就那樣靜靜地躺著,像睡著了一般魏蔗。 火紅的嫁衣襯著肌膚如雪砍的。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天莺治,我揣著相機與錄音挨约,去河邊找鬼。 笑死产雹,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的翁锡。 我是一名探鬼主播蔓挖,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼馆衔!你這毒婦竟也來了瘟判?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤角溃,失蹤者是張志新(化名)和其女友劉穎拷获,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體减细,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡冗荸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年膝晾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡进萄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出糖声,到底是詐尸還是另有隱情抽莱,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布纸型,位于F島的核電站拇砰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏狰腌。R本人自食惡果不足惜除破,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望癌别。 院中可真熱鬧皂岔,春花似錦、人聲如沸展姐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至教馆,卻和暖如春逊谋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背土铺。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工胶滋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人悲敷。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓究恤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親后德。 傳聞我的和親對象是個殘疾皇子部宿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容