原文,Flutter, what are Widgets, RenderObjects and Elements?git
你可曾想過 Flutter 是如何處理 Widgets 並將他們轉換成像素顯示在屏幕上的?尚未?程序員
你應該思考一下。github
是否理解系統的底層實現原理是區分一個優秀程序員的關鍵。canvas
當你瞭解什麼最有效的時候,你才能更輕鬆地建立佈局和特效,從而節省大量的時間。框架
這篇文章的目的是向你介紹 Flutter 內部的工做原理,咱們將從不一樣的角度來看 Flutter,理解它究竟是如何工做的。less
你可能已經知道如何使用 StatelessWidget
和 StatefulWidget
了,可是它們只是用來組裝控件的容器,佈局和繪製的工做是在其餘地方完成的。ide
我強烈建議你打開本身喜歡的 IDE 並繼續閱讀,只有看着實際的代碼才能讓你感到「噢,原來是這樣的」。在 Intellij 中,你能夠經過雙擊 shift 並輸入類名來查找相應代碼。函數
爲了熟悉 Flutter 工做的基本原理,咱們先來看一個最基礎的控件 Opacity
,它將是一個很好的例子。佈局
Opacity
接收一個 child,因此你能夠用 Opacity
來包裝任意的 Widget 從而改變它的外觀。另外,它還接收一個名爲 opacity
的屬性,用來設置控件的不透明度,取值在 0.0 到 1.0 之間。動畫
Opacity
是一個 SingleChildRenderObjectWidget
。
這個類的繼承關係以下:
Opacity → SingleChildRenderObjectWidget → RenderObjectWidget → Widget
相應的,StatelessWidget
和 StatefulWidget
的繼承關係以下:
StatelessWidget / StatefulWidget→Widget
它們的不一樣之處在於,Stateless / StatefulWidget 只是將其餘 Widget
組裝起來,而 Opacity
會真正地影響 Widget
的繪製。
可是若是你去那些代碼中找的話,你是不可能找到任何與屬性 opacity 相關的繪製代碼。
那是由於 Widget 僅僅只持有控件的配置信息。好比這個例子中,控件 Opacity
只是用來持有屬性 opacity 的。
這也就是你每次均可以在
build()
函數中新建widget
的緣由。構建widget
的過程並不耗費資源,由於 Wiget 只是用來保存屬性的容器。
那麼渲染是在哪完成的呢?
答案是 RenderingObject
。
正如你能從名字中猜出的那樣,RenderingObject
負責渲染相關的工做。
Opacity
經過下面這些方法來建立和更新 RenderingObject:
@override
RenderOpacity createRenderObject(BuildContext context) => new RenderOpacity(opacity: opacity);
@override
void updateRenderObject(BuildContext context, RenderOpacity renderObject) {
renderObject.opacity = opacity;
}
複製代碼
除了繪製,Opacity
和它的 child 幾乎如出一轍,它用 child 的大小做爲自身大小。在繪製它的 child 以前,它給 child 增長了一個不透明度。
因此,RenderOpacity
須要實現包括佈局、點擊檢測、計算大小在內的全部的函數,並將其轉交給它的 child 來完成(也就至關於一個 child 的代理)。
RenderOpacity
繼承於 RenderProxyBox
,RenderProxyBox
中實現了向 child 的工做交接。
double get opacity => _opacity;
double _opacity;
set opacity(double value) {
_opacity = value;
markNeedsPaint();
}
複製代碼
在 setter 方法中除了設置字段的值外,還調用了 markNeedsPaint()
(或者 markNeedsLayout()
),顧名思義,它告訴系統「我已經發生了改變,請從新進行繪製(或佈局)」。
在 RenderOpacity
中,咱們找到了下面這個方法:
@override
void paint(PaintingContext context, Offset offset) {
context.pushOpacity(offset, _alpha, super.paint);
}
複製代碼
PaintingContext
就是進行繪製操做的畫布,這裏經過在 canvas 上調用名爲pushOpacity
的方法來實現不透明度的控制。
Opacity
不是 StatelessWidget
或者 StatefulWidget
,而是 SingleChildRenderObjectWidget
;Opacity
存儲了一個雙精度值的不透明度;RenderProxyBox
的 RenderOpacity
完成的;Opacity
並不改變 child 的其餘行爲,因此它的每一個方法都僅僅只是 child 的代理;paint
方法並調用 pushOpacity
,RenderOpacity 實現了向 Widget 添加不透明度的需求。記住,Widget 只是一個配置,RenderObject
負責管理佈局、繪製等操做。
在 Flutter 中,你基本上一直都在不停的建立 Widgets,當 build()
方法被調用時,你建立了一堆 Widgets。
每當有什麼變化產生的時候,build()
方法都會被調用。例如播放一個動畫,build()
方法就會被頻繁調用。這意味着你不能老是從新構建一整顆渲染樹,相反,你應該作的知識去更新這顆樹。
你沒法獲取一個 widget 在屏幕上的位置和大小,由於 widget 就像一張藍圖,它並不是真實地顯示在屏幕之上,它只描述了底層渲染對象應該具備的那些屬性。
Element
是這顆巨大的控件樹上的實體。
在第一次建立 Widget
的時候,會對應建立一個 Element
, 而後將該元素插入樹中。若是以後 Widget
發生了變化,則將其與舊的 Widget
進行比較,而且相應地更新 Element
。重要的是,Element
被不會重建,只是更新而已。
Elements 是 Flutter 核心框架的重要組成部分,顯然它並不只僅如此,但目前對咱們來講,知道這些就足夠了。
SingleChildRenderObjectWidget
中建立了它:
@override
SingleChildRenderObjectElement createElement() => new SingleChildRenderObjectElement(this);
複製代碼
而 SingleChildRenderObjectElement
則是一個僅擁有一個 child 的元素。
這僅僅是爲了平滑的 API,由於常見的狀況是 Widget
須要一個 RenderObject
而不是自定義 Element
。RenderObject
實際是由 Element
來建立的,讓咱們來看看。
SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);
複製代碼
在構造函數中,SingleChildRenderObjectElement
拿到了一個 RenderObjectWidget
的引用(其中包含了建立 RenderObject
的方法)。
Element
經過 mount
方法插入到 Element Tree 中,這裏就是 Element
建立 RenderObject
的地方:
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
複製代碼
一旦 Element
被掛載到樹上,它便會向 Widget
請求 「請給我你要使用的 RenderObject,這樣我就能保存它了」。
這就是 Opacity
控件內部的工做方式。 這篇文章的目標是向你介紹 widget 以外的世界。這裏任然還有不少話題要討論,但我但願你已經很好地理解了其內部的工做原理。