[譯]Flutter,什麼是Widgets、RenderObjects和Elements?

NO IMAGE

原文,Flutter, what are Widgets, RenderObjects and Elements?

你可曾想過 Flutter 是如何處理 Widgets 並將他們轉換成像素顯示在屏幕上的?還沒有?

你應該思考一下。

是否理解系統的底層實現原理是區分一個優秀程序員的關鍵。

當你瞭解什麼最有效的時候,你才能更輕鬆地創建佈局和特效,從而節省大量的時間。

這篇文章的目的是向你介紹 Flutter 內部的工作原理,我們將從不同的角度來看 Flutter,理解它到底是如何工作的。

讓我們開始吧

你可能已經知道如何使用 StatelessWidgetStatefulWidget 了,但是它們只是用來組裝控件的容器,佈局和繪製的工作是在其他地方完成的。

我強烈建議你打開自己喜歡的 IDE 並繼續閱讀,只有看著實際的代碼才能讓你感到“噢,原來是這樣的”。在 Intellij 中,你可以通過雙擊 shift 並輸入類名來查找相應代碼。

Opacity

為了熟悉 Flutter 工作的基本原理,我們先來看一個最基礎的控件 Opacity,它將是一個很好的例子。

Opacity 接收一個 child,所以你可以用 Opacity 來包裝任意的 Widget 從而改變它的外觀。另外,它還接收一個名為 opacity 的屬性,用來設置控件的不透明度,取值在 0.0 到 1.0 之間。

Widget

Opacity 是一個 SingleChildRenderObjectWidget

這個類的繼承關係如下:

Opacity → SingleChildRenderObjectWidget → RenderObjectWidget → Widget

相應的,StatelessWidgetStatefulWidget 的繼承關係如下:

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 繼承於 RenderProxyBoxRenderProxyBox 中實現了向 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 存儲了一個雙精度值的不透明度;
  • 佈局和渲染等操作實際是由繼承至 RenderProxyBoxRenderOpacity 完成的;
  • 因為 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 而不是自定義 ElementRenderObject 實際是由 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 之外的世界。這裡任然還有很多話題要討論,但我希望你已經很好地理解了其內部的工作原理。

相關文章

Android巧妙利用CompoundDrawables

Git三大特色之Branch(分支)

一套流程認知Git常用命令

請回答:Git是什麼?