原文在這裡。
本文詳細解釋瞭如何把Widget轉化成了螢幕上的一個個畫素點
想要成為一個更好的開發,瞭解底層的實現技術幾乎是必不可少的。你可以更容易的建立自定義的佈局和特效,如果你學習了這些底層技術是如何工作的。也可以讓少在電腦前加幾個晚上的班。
本文的目的就是要介紹Flutter之下的技術,並讓你理解他們的是如何執行的。
## 我們現在開始
你也許已經知道如何使用StatelessWidget和StatefulWidget。但是這些Widget只是把其他的Widget組合到了一起。在螢幕加上的佈局和繪製是發生在別的地方的。
**我強烈建議你開啟你最心愛的編輯工具,然後跟著本文一步一步的檢視實際程式碼是如何實現的並一路“原來如此”過去。、
## Opacity元件
從Opacity這個元件開始是最好不過了。這個Widget足夠的簡單,而且是一個絕佳的例子。
它只接受一個子元件。因此你可以把任何一個widget放進Opacity裡改變其顯示。另外還有一個值opacity
,在0.0和1.0之間。它用來控制透明度。
Opacity
是SingleChildRenderObjectWidget
的子類。繼承的路徑是這樣的:
Opacity -> SingleChildRenderObjectWidget -> RenderObjectWidget -> Widget。
而StatelessWidget
和StatefulWidget
是這樣的繼承路徑:
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元件工作的原理。
我的目標是用這篇文章來介紹元件之下的原理。還有很多的內容沒有覆蓋到,但是我希望這篇文章至少可以讓你初窺門徑。