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

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

你可曾想過 Flutter 是如何處理 Widgets 並將他們轉換成像素顯示在屏幕上的?尚未?程序員

你應該思考一下。github

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

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

這篇文章的目的是向你介紹 Flutter 內部的工做原理,咱們將從不一樣的角度來看 Flutter,理解它究竟是如何工做的。less

讓咱們開始吧

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

我強烈建議你打開本身喜歡的 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 以外的世界。這裏任然還有不少話題要討論,但我但願你已經很好地理解了其內部的工做原理。

相關文章
相關標籤/搜索