Flutter - 什麼是Widget,RenderObject和Element

小紅星閃啊閃發表於2020-05-05

原文在這裡

本文詳細解釋瞭如何把Widget轉化成了螢幕上的一個個畫素點

想要成為一個更好的開發,瞭解底層的實現技術幾乎是必不可少的。你可以更容易的建立自定義的佈局和特效,如果你學習了這些底層技術是如何工作的。也可以讓少在電腦前加幾個晚上的班。

本文的目的就是要介紹Flutter之下的技術,並讓你理解他們的是如何執行的。

## 我們現在開始

你也許已經知道如何使用StatelessWidget和StatefulWidget。但是這些Widget只是把其他的Widget組合到了一起。在螢幕加上的佈局和繪製是發生在別的地方的。

**我強烈建議你開啟你最心愛的編輯工具,然後跟著本文一步一步的檢視實際程式碼是如何實現的並一路“原來如此”過去。、

## Opacity元件
從Opacity這個元件開始是最好不過了。這個Widget足夠的簡單,而且是一個絕佳的例子。

它只接受一個子元件。因此你可以把任何一個widget放進Opacity裡改變其顯示。另外還有一個值opacity,在0.0和1.0之間。它用來控制透明度。

OpacitySingleChildRenderObjectWidget的子類。繼承的路徑是這樣的:

Opacity -> SingleChildRenderObjectWidget -> RenderObjectWidget -> Widget。

StatelessWidgetStatefulWidget是這樣的繼承路徑:

StatelessWidget/StatefulWidget -> Widget

不同之處就在於StatelessWidget和StatefulWidget只是把不同的元件(Widget)組合在一起,而Opacity元件會控制一個元件如何繪製。

但是如果你想從Opacity的程式碼裡找到一些繪製畫素的線索,這幾乎是徒勞的。這是因為一個Widget只包含了配置資訊,比如Opacity元件只包含了一個opacity的值。

這就是為什麼你可以在一個元件的build方法裡建立新的Widget,裡面並不會包含耗費資源的構建元件的程式碼,僅僅是包含了一些構建需要的資訊。

繪製

繪製的時候會發生什麼呢?

RenderObject

繪製都在RenderObject裡進行。這個單從名稱裡也可以知道了。Opacit元件這樣建立和更新RenderObject:

@override
RenderOpacity createRenderObject(BuildContext context) => new RenderOpacity(opacity: opacity);

@override
void updateRenderObject(BuildContext context, RenderOpacity renderObject) {
  renderObject.opacity = opacity;
}

RenderOpacity

Opacity元件把自身的size和子元件的size設定成一樣的值。它基本上和它子元件的每一方面都一樣,除了繪製。在繪製子元件之前新增了opacity值。

在這個情況下, RenderOpacity需要實現所有的方法(比如,執行佈局、碰撞檢測和計算size)然後交給子類去執行具體的工作。

RenderOpacity繼承了RenderProxyBox(這個類mixin了一些其他 的類)。這些類都分別實現了上面提到的那些方法。

double get opacity => _opacity;
double _opacity;
set opacity(double value) {
  _opacity = value;
  markNeedsPaint();
}

我(作者)刪除了大部分的程式碼,要看全部程式碼可以在這裡看。

getter把私有的值暴露出去。setter裡面會呼叫markNeedsPaint()或者markNeedsLayout()。就如名字所言,它會通知系統“這個元件發生了改變,需要重繪或者重新佈局”

RenderOpacity裡面還有如下的程式碼:

@override
void paint(PaintingContext context, Offset offset) {
    context.pushOpacity(offset, _alpha, super.paint);
}

PaintingContext就是一個畫布。在這個畫布上有一個方法叫做pushOpacity

這一行就是opacity的具體實現。

回顧

  • Opacity不是一個StatelessWidget或者StatefullWidget而是一個SingleChildRendeObjectWidget
  • 元件只包含了繪製的時候需要用到的資訊。比如,Opacity元件包含了一個opacity值。
  • RenderOpacity,繼承自RenderProxyBox,執行了具體的佈局和繪製動作
  • 因為Opacity元件和它的子元件基本上完全一樣,所以它代理了其子元件的所有方法
  • 它override了繪製(paint)方法,這個方法會給Opacity的子元件新增一個特定的opacity值

就這樣了麼?

記住元件(widget)只不過是一個配置,RenderObject才是管理佈局和繪製的。

在Flutter裡,你基本上一直都會建立新的Widget,當build()方法被呼叫的時候你就建立了一大堆的元件。當某些變化發生的時候,build方法基本上都會被呼叫。比如一個動畫裡,build方法更會被經常呼叫。最好是不要每次都重新繪製整個子樹,更新會更好一些。

你不能獲得一個元件在螢幕上的大小或者位置,因為一個元件姿勢一個藍圖,並不是實際繪製在介面上的。它只包含了render object需要用的資訊。

Element

Element是一個元件樹的實際元件。

發生了什麼

第一次一個元件被建立的時候,會有一個Element被建立,並且兩者互相關聯。之後這個element被插入到了一個樹裡。如果元件(widget)發生了更改,它會和舊的元件對比,並對應的更新element。最重要的是在這個時候element不會重新建立,只會被更新。

Element是flutter核心的一部分,不過現在不需要知道更多的內容。這些就足夠了。

Opacity元件的Element是在哪裡建立的

請好奇的同學看過來。在SingleChildRenderObectWidget

@override
SingleChildRenderObjectElement createElement() => new SingleChildRenderObjectElement(this);

SingleChildRenderObjectElement也只是一個有一個child的Element。

Element建立RenderObject

如果是Element建立了RenderObject,那麼Opacity元件的RenderObject怎麼是自己建立的呢?

基本上是因為Opacity元件只是需要一個RenderObject但是不需要一個定製的Element。

看下面的程式碼:

SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);

SingleChildRenderObjectElement有一個RenderObjectWidget的引用(這裡也包含了建立RenderObject的方法)。

RenderObjectElement#mount方法裡element被插入到了element樹裡:

@override
void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  _renderObject = widget.createRenderObject(this);
  attachRenderObject(newSlot);
  _dirty = false;
}

在Element掛載的時候,它才會讓元件建立一個render object。

最後

這就是Opacity元件工作的原理。

我的目標是用這篇文章來介紹元件之下的原理。還有很多的內容沒有覆蓋到,但是我希望這篇文章至少可以讓你初窺門徑。

相關文章