轉載請聯繫: 微信號: michaelzhoujay程序員
原文請訪問個人博客微信
若是你要使用Flutter開始構建一個頁面,那麼你可能從Widget開始一層一層搭建。less
可是你曾經想過這些Widget是如何繪製到屏幕上的嗎?不少同窗應該會熟悉Flutter的三個樹形結構:ide
站在Flutter程序員的角度,打交道最多的應該是Widget了,那麼下面我帶領你們從Flutter源碼讀起來看一下一個Widget是如何繪製到屏幕上的。函數
首先咱們來回憶一個熟悉的Widget:Opacity
。佈局
這個Widget通常用來給一個佈局加上透明度,它只有一個參數 opacity
,1.0表示徹底不透明,0.0表示徹底透明。動畫
在源碼裏能夠看到 Opacity 的繼承關係:ui
Opacity -> SingleChildRenderObjectWidget -> RenderObject -> Widgetthis
咱們比較一下最簡單的 StatelessWidget/StatefulWidget :spa
StatelessWidget -> Widget
StatefulWidget -> Widget
能夠看到Opacity
多了SingleChildRenderObjectWidget
和RenderObject
這層關係,那這層關係究竟是幹什麼的呢?咱們接着往下看源碼。
經過看RenderObject
的代碼,咱們知道只要繼承了 RenderObject
就須要實現createRenderObject
方法。
@override
RenderOpacity createRenderObject(BuildContext context) {
return RenderOpacity(
opacity: opacity,
alwaysIncludeSemantics: alwaysIncludeSemantics,
);
}
複製代碼
能夠發現其實最終返回了一個RenderOpacity
大概看一下RenderOpacity
的代碼,能夠知道這裏實現了最後的所謂的「繪製」也就是paint
的代碼:
void paint(PaintingContext context, Offset offset) {
if (child != null) {
if (_alpha == 0) {
return;
}
if (_alpha == 255) {
context.paintChild(child, offset);
return;
}
assert(needsCompositing);
context.pushOpacity(offset, _alpha, super.paint);
}
}
複製代碼
這裏的PaintingContext
其實是個Canvas,因此最重要的代碼就是context.pushOpacity
這個方法的調用,最終用上了 _alpha
,而這個_alpha
由最上層Opacity
Widget的惟一參數opacity
計算而來。
因此咱們來總結一下這裏面的關係:
Opacity
直接繼承SingleChildRenderObjectWidget
Widget
不直接參與繪製,僅僅存儲繪製須要的信息RenderOpacity
在這裏承擔了實際的 layout/render 邏輯咱們平常所用的Widget僅僅是一些配置,RenderObject作了實際的 layout/render 等工做。這一點和Android的 View
、iOS的 UIView
有本質的區別,請注意。
在Flutter中,只要是build()
方法被調用了,就會建立一堆Widget,這個是OK的,上面說過Widget僅僅是配置文件,因此即便頻繁地 建立/重建 它們,是不會帶來界面的刷新的。
回想一下,若是你要在Flutter裏建立一個動畫,通常來講,須要在指定的Widget之上套上一層XXXTransition,而後動畫開始後用animationController
來不斷地讓Widget刷新重建,可是整個頁面不會所以從新刷新。
因此,Widget的重建和頁面真正的從新渲染沒有直接的聯繫,那麼頁面何時纔會從新渲染呢,咱們接着看源碼。
上面咱們以Opacity
爲例,從源碼中看出了它的繼承關係,而且推導出一些結論,這裏面都沒有涉及到Element
,但實際上Element也十分重要。那什麼是Element呢?源碼裏的註釋以下:
An instantiation of a Widget at a particular location in the tree.
咱們知道,Widget是immutable的,也就是說一旦有了變化,Widget是反覆重建的,你在代碼裏寫的Widget的構造函數會被從新執行。那對應的,確定得有一個東西來消化掉這些變化中的不變,來作cache。
讓程序員不去保存Widgets是好的,由於他不會由於管理無數的Widget並且煩惱,一旦有變化就當整個頁面都在變化,Widget的每一幀對應了一個State。這樣,一個指定的state就能精確描述一個Widget該怎麼展現。也就是說,程序員只要管理好狀態,那麼就能管理好整個頁面變化的邏輯。
當一個Widget首次被建立的時候,那麼這個Widget會過Widget.createElement
inflate成一個element
,掛在 element tree 上。
此後,當State發生變化,則Widget重建,但Element只會updates。
也就是說咱們能大概有一個如下的一個流程:
程序員寫Widget -> Widget造成Element -> Element構建 RenderObject -> RenderObject描述Canvas繪製 -> 交給FlutterEngine 光柵化Rasterize
回到咱們今天的主角Opacity
,從上面的分析,咱們知道Opacity也是Widget,因此理應有對應的Element,可是上面看到爲啥直接Widget就本身返回一個RenderOpacity
呢?看起來是Widget本身建立了RenderObject。那麼它的element是何時建立的呢?看源碼:
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
...
@override
SingleChildRenderObjectElement createElement() => new SingleChildRenderObjectElement(this);
...
}
複製代碼
Opacity繼承自SingleChildRenderObjectWidget
, 這裏面的SingleChildRenderObjectElement
,其實是隻有一個child的Element。當第Widget第一次建立的時候,createElement
會被調用,返回一個SingleChildRenderObjectElement
那它的 RenderObject 其實是被這個 Element 建立的,看一下爲何:
class SingleChildRenderObjectElement extends RenderObjectElement {
SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);
...
}
複製代碼
這個SingleChildRenderObjectElement
構造函數接受了一個SingleChildRenderObjectWidget
參數,它負責建立一個RenderObject
。那麼咱們看這個 renderobject 實際在哪裏建立的:
class SingleChildRenderObjectElement {
...
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_child = updateChild(_child, widget.child, null);
}
...
}
複製代碼
SingleChildRenderObjectElement裏有個mount方法,看起來沒啥,不過沒事兒咱們往父類裏看:
class RenderObjectElement extends Element {
...
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
assert(() { _debugUpdateRenderObjectOwner(); return true; }());
assert(_slot == newSlot);
attachRenderObject(newSlot);
_dirty = false;
}
...
複製代碼
終於看到magic了,因此在mount
方法裏,element調用了widget.createRenderObject
,並attach。
注意,這些都發生在mount
方法裏,意味着只有element mounted的時候纔會執行,因此只有一次。
參考文獻: