從源碼瞭解Flutter的渲染基礎:Widget/Element/RenderObject

轉載請聯繫: 微信號: michaelzhoujay程序員

原文請訪問個人博客微信


若是你要使用Flutter開始構建一個頁面,那麼你可能從Widget開始一層一層搭建。less

可是你曾經想過這些Widget是如何繪製到屏幕上的嗎?不少同窗應該會熟悉Flutter的三個樹形結構:ide

  • Widget Tree
  • Element Tree
  • RenderObject Tree

站在Flutter程序員的角度,打交道最多的應該是Widget了,那麼下面我帶領你們從Flutter源碼讀起來看一下一個Widget是如何繪製到屏幕上的。函數

Opacity

首先咱們來回憶一個熟悉的Widget:Opacity佈局

這個Widget通常用來給一個佈局加上透明度,它只有一個參數 opacity,1.0表示徹底不透明,0.0表示徹底透明。動畫

在源碼裏能夠看到 Opacity 的繼承關係:ui

Opacity -> SingleChildRenderObjectWidget -> RenderObject -> Widgetthis

咱們比較一下最簡單的 StatelessWidget/StatefulWidget :spa

StatelessWidget -> Widget

StatefulWidget -> Widget

能夠看到Opacity多了SingleChildRenderObjectWidgetRenderObject這層關係,那這層關係究竟是幹什麼的呢?咱們接着往下看源碼。

createRenderObject 方法

經過看RenderObject的代碼,咱們知道只要繼承了 RenderObject就須要實現createRenderObject方法。

@override
  RenderOpacity createRenderObject(BuildContext context) {
    return RenderOpacity(
      opacity: opacity,
      alwaysIncludeSemantics: alwaysIncludeSemantics,
    );
  }
複製代碼

能夠發現其實最終返回了一個RenderOpacity

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由最上層OpacityWidget的惟一參數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的重建和頁面真正的從新渲染沒有直接的聯繫,那麼頁面何時纔會從新渲染呢,咱們接着看源碼。

Element

上面咱們以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的時候纔會執行,因此只有一次。


參考文獻:

1. Flutter, what are Widgets, RenderObjects and Elements?

2. The Layer Cake

相關文章
相關標籤/搜索