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

Joe_H發表於2019-03-04

原文,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 之外的世界。這裡任然還有很多話題要討論,但我希望你已經很好地理解了其內部的工作原理。

相關文章