你可曾想過 Flutter 是如何處理 Widgets 並將他們轉換成畫素顯示在螢幕上的?還沒有?
你應該思考一下。
是否理解系統的底層實現原理是區分一個優秀程式設計師的關鍵。
當你瞭解什麼最有效的時候,你才能更輕鬆地建立佈局和特效,從而節省大量的時間。
這篇文章的目的是向你介紹 Flutter 內部的工作原理,我們將從不同的角度來看 Flutter,理解它到底是如何工作的。
讓我們開始吧
你可能已經知道如何使用 StatelessWidget
和 StatefulWidget
了,但是它們只是用來組裝控制元件的容器,佈局和繪製的工作是在其他地方完成的。
我強烈建議你開啟自己喜歡的 IDE 並繼續閱讀,只有看著實際的程式碼才能讓你感到“噢,原來是這樣的”。在 Intellij 中,你可以通過雙擊 shift 並輸入類名來查詢相應程式碼。
Opacity
為了熟悉 Flutter 工作的基本原理,我們先來看一個最基礎的控制元件 Opacity
,它將是一個很好的例子。
Opacity
接收一個 child,所以你可以用 Opacity
來包裝任意的 Widget 從而改變它的外觀。另外,它還接收一個名為 opacity
的屬性,用來設定控制元件的不透明度,取值在 0.0 到 1.0 之間。
Widget
Opacity
是一個 SingleChildRenderObjectWidget
。
這個類的繼承關係如下:
Opacity → SingleChildRenderObjectWidget → RenderObjectWidget → Widget
相應的,StatelessWidget
和 StatefulWidget
的繼承關係如下:
StatelessWidget / StatefulWidget→Widget
它們的不同之處在於,Stateless / StatefulWidget 只是將其他 Widget
組裝起來,而 Opacity
會真正地影響 Widget
的繪製。
但是如果你去那些程式碼中找的話,你是不可能找到任何與屬性 opacity 相關的繪製程式碼。
那是因為 Widget 僅僅只持有控制元件的配置資訊。比如這個例子中,控制元件 Opacity
只是用來持有屬性 opacity 的。
這也就是你每次都可以在
build()
函式中新建widget
的原因。構建widget
的過程並不耗費資源,因為 Wiget 只是用來儲存屬性的容器。
Rendering
那麼渲染是在哪完成的呢?
答案是 RenderingObject
。
正如你能從名字中猜出的那樣,RenderingObject
負責渲染相關的工作。
Opacity
通過下面這些方法來建立和更新 RenderingObject:
@override
RenderOpacity createRenderObject(BuildContext context) => new RenderOpacity(opacity: opacity);
@override
void updateRenderObject(BuildContext context, RenderOpacity renderObject) {
renderObject.opacity = opacity;
}
複製程式碼
RenderOpacity
除了繪製,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
;- Widget 僅用於儲存渲染所需要的資訊;
- 在這裡,
Opacity
儲存了一個雙精度值的不透明度; - 佈局和渲染等操作實際是由繼承至
RenderProxyBox
的RenderOpacity
完成的; - 因為
Opacity
並不改變 child 的其他行為,所以它的每個方法都僅僅只是 child 的代理; - 通過過載
paint
方法並呼叫pushOpacity
,RenderOpacity 實現了向 Widget 新增不透明度的需求。
That’s it? Kind of.
記住,Widget 只是一個配置,RenderObject
負責管理佈局、繪製等操作。
在 Flutter 中,你基本上一直都在不停的建立 Widgets,當 build()
方法被呼叫時,你建立了一堆 Widgets。
每當有什麼變化產生的時候,build()
方法都會被呼叫。例如播放一個動畫,build()
方法就會被頻繁呼叫。這意味著你不能總是重新構建一整顆渲染樹,相反,你應該做的知識去更新這顆樹。
你無法獲取一個 widget 在螢幕上的位置和大小,因為 widget 就像一張藍圖,它並非真實地顯示在螢幕之上,它只描述了底層渲染物件應該具有的那些屬性。
Element
Element
是這顆巨大的控制元件樹上的實體。
基本上會發生什麼:
在第一次建立 Widget
的時候,會對應建立一個 Element
, 然後將該元素插入樹中。如果之後 Widget
發生了變化,則將其與舊的 Widget
進行比較,並且相應地更新 Element
。重要的是,Element
被不會重建,只是更新而已。
Elements 是 Flutter 核心框架的重要組成部分,顯然它並不僅僅如此,但目前對我們來說,知道這些就足夠了。
在 Opacity 示例中,element 是在哪建立的?
SingleChildRenderObjectWidget
中建立了它:
@override
SingleChildRenderObjectElement createElement() => new SingleChildRenderObjectElement(this);
複製程式碼
而 SingleChildRenderObjectElement
則是一個僅擁有一個 child 的元素。
Element 建立 RenderObject,但在示例中是 Widget 建立的 RenderObject?
這僅僅是為了平滑的 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 之外的世界。這裡任然還有很多話題要討論,但我希望你已經很好地理解了其內部的工作原理。