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)。
當(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è)置…)
- Future或http響應(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 Frame被Flutter框架攔截,后者將查找與更新布局有關(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的特殊版本檐蚜。
從視覺上講魄懂,我們可以將所有這些表示如下:
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引擎之間的交互睛约。
讓我們看一下這些“主要”綁定鼎俘。
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)如下:
如您所見趾诗,這看起來像一棵樹,其中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個玩偶始腾,但后者包含另一個州刽,然后依次包含另一個,依此類推...
當(dāng)Flutter將所有widget膨脹到屏幕的一部分時浪箭,這類似于獲取所有不同的俄羅斯玩偶穗椅,即全部的一部分。
下圖顯示了最終Widget層次結(jié)構(gòu)的一部分奶栖,與先前的代碼相對應(yīng)匹表。 用黃色突出顯示了代碼中提到的Widget,以便您可以在生成的局部Widget樹中發(fā)現(xiàn)它們宣鄙。
重要說明
“Widget樹”的用語僅是為了使程序員易于理解袍镀,因為程序員正在使用小部件,但是在Flutter中沒有Widget樹冻晤!
實際上苇羡,正確地說,我們應(yīng)該說:“Element樹”
現(xiàn)在是時候介紹Element的概念了……
每個小部件對應(yīng)一個元素鼻弧。 元素彼此鏈接并形成一棵樹设江。 因此,元素是樹中某物的引用攘轩。
首先叉存,將元素視為有父節(jié)點并可能子節(jié)點的節(jié)點。 通過父節(jié)點關(guān)系鏈接在一起度帮,我們得到一個樹形結(jié)構(gòu)歼捏。
如上圖所示,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樹是Widget和RenderObject之間的實際鏈接。
但是痰洒,為什么Widget會創(chuàng)建Element瓢棒?
三個主要Widget類別
在Flutter中,Widget分為3個主要類別丘喻,我個人稱這些類別為(但是這是我將他們分類的方式):
-
代理
這些Widget的主要作用是保存一些信息脯宿,這些信息需要供Widget使用,Widget是樹結(jié)構(gòu)的一部分泉粉,以代理為根连霉。此類Widget的典型示例是InheritedWidget或LayoutId。
這些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中荆陆,整個機制依賴于使Element或renderObject無效(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)SchedulerBinding從Flutter引擎接收到onDrawFrame()請求時會發(fā)生什么情況。
第一步:Element
WidgetsBinding被調(diào)用逗栽,后者首先考慮與Element相關(guān)的??更改盖袭。
由于BuildOwner負(fù)責(zé)處理Element樹,因此WidgetsBinding會調(diào)用buildOwner的buildScope方法彼宠。
此方法迭代無效元素(=臟)的列表鳄虱,并要求它們進行重建。
rebuild()方法的主要原理是:
- 請求元素進行rebuild()凭峡,這導(dǎo)致大多數(shù)情況拙已,調(diào)用了該element所引用的Widget的build方法(=方法Widget build(BuildContext context){…})。這個* build()*方法返回一個新的Widget摧冀。
- 如果元素沒有子元素倍踪,則將新Widget膨脹(見下文)系宫,否則
- 將新Widget與該Element的子Element引用的Widget進行比較。
如果可以交換(= 相同的Widget類型和鍵)建车,則進行更新扩借,并保留子Element。
如果無法交換它們缤至,則子Element將被卸載(? 被丟棄)潮罪,并且新的Widget會膨脹。 - Widget的膨脹導(dǎo)致創(chuàng)建一個新Element领斥,該Element將作為該Element的新Element掛載嫉到。 (掛載 =已插入到Element樹中)
下面的動畫試圖使這種解釋更加直觀。
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)用RendererBinding的drawFrame方法。
下面的局部圖顯示了在drawFrame()請求期間執(zhí)行的操作順序钓株。
在此步驟中实牡,執(zhí)行以下活動:
- 要求每個標(biāo)有dirty的renderObject進行布局(意味著計算其尺寸和幾何形狀)
- 使用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會攔截它总滩,并進行一些緩沖纲堵,并:
- 轉(zhuǎn)換Flutter引擎發(fā)出的坐標(biāo),以匹配設(shè)備像素比率闰渔,然后
- 請求renderView提供所有RenderObject的列表席函,該列表覆蓋了包含事件坐標(biāo)的屏幕的一部分
- 然后迭代該renderObject列表,并將相關(guān)事件分派給它們中的每一個冈涧。
- 當(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)部的工作原理邀窃,這是全局圖:
BuildContext
最后的話...
如果您回想起顯示不同element類型的圖,您很可能會注意到基礎(chǔ)Element的簽名:
abstract class Element extends DiagnosticableTree implements BuildContext {
...
}
這就是著名的 BuildContext.
什么是BuildContext呢?
BuildContext是一個接口假哎,它定義了可以由Element以協(xié)調(diào)的方式實現(xiàn)的一系列getter和方法瞬捕。
特別是,BuildContext主要用于StatelessWidget和StatefulWidget的build()方法或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)了解到BuildContext是element,出于樂趣陶缺,我想向您展示另一種使用它的方式...
以下無用代碼使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牍汹,Element,BuildContext柬泽, RenderObject之類的關(guān)鍵概念并不總是顯而易見的慎菲。
我希望本文可能有用。
敬請期待新文章锨并。 同時露该,祝您編碼愉快。