Widget、RenderObject 與 Element

Flutter程式設計開發發表於2020-07-05

Widget、RenderObject 與 Element

Widget、RenderObject 與 Element

我們在學習 Flutter 的時候,可能經常看到三個名詞: Widget、RenderObject 和 Element ,弄懂這幾個概念可能也是入門 Flutter 框架原理的第一步。

一、 Widget

在 Flutter 中,萬物皆是 Widget,無論是可見的還是功能型的,那麼 Widget 究竟是什麼呢?

按照慣例,先看官方文件。

Widget、RenderObject 與 Element

  • Widget 的作用是用來儲存 Element 的配置資訊的。
  • Widget 本身是不可變的。
  • Element 根據 Widget 裡面儲存的配置資訊來管理渲染樹。
  • Widget 可以多次的插入到 Widget 樹中,每插入一次,Element 都要重新裝載一遍 Widget 。
  • Widget 裡面的 key 屬性用來決定依賴這個 Widget 的 Element 在 Element 樹中是更新還是移除。

接下來看一下 Widget 的定義。

abstract class Widget {
  const Widget({ this.key });
  final Key key;
  
  @protected
  Element createElement();
  
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
   return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}
複製程式碼

通過上面 Widget 的定義,可以看到有兩個重要的方法,一個是通過 createElement 來建立 Element 物件的,一個是根據 key 來決定更新行為的 canUpdate 方法。

以 Opacity 為例,Opacity 做為一個 Widget ,只儲存另一個配置資訊:opacity,這個屬性決定了透明度,範圍在 0 到 1 之間。

Opacity 既然做為一個 Widget,肯定是 Widget 的子類,其繼承關係如下:

Opacity → SingleChildRenderObjectWidget → RenderObjectWidget → Widget
複製程式碼

Opacity 的定義如下:

class Opacity extends SingleChildRenderObjectWidget {

  const Opacity({
    Key key,
    @required this.opacity,
    this.alwaysIncludeSemantics = false,
    Widget child,
  }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
       assert(alwaysIncludeSemantics != null),
       super(key: key, child: child);

  final double opacity;
  
  @override
  RenderOpacity createRenderObject(BuildContext context) {
    return RenderOpacity(
      opacity: opacity,
      alwaysIncludeSemantics: alwaysIncludeSemantics,
    );
  }

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

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DoubleProperty('opacity', opacity));
    properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
  }
}
複製程式碼

通過上面的程式碼,可以看到 opacity 是 final 型別的屬性,只能做為建構函式引數傳遞進去,不可改變,因此如果要更新這個屬性,必須新建一個 Opcity 物件,這也是為什麼我們程式碼裡的 Widget build(BuildContext context) 方法裡面每次 build 都會建立新的物件例項的原因。

二、RenderObject

Widget、RenderObject 與 Element

  • RenderObject 是做為渲染樹中的物件存在的。
  • RenderObject 不會定義約束關係,也就是不會對子控制元件的佈局位置、大小等進行管理。
  • RenderObject 中有一個 parentData 屬性,這個屬性用來儲存其孩子節點的特定資訊,如子節點位置,這個屬性對其孩子是透明的。

RenderObject 的定義如下

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
  ParentData parentData;
  Constraints _constraints;
  void layout(Constraints constraints, { bool parentUsesSize = false }) {
    
  }
  void paint(PaintingContext context, Offset offset) { }
  
  void performLayout();
  void markNeedsPaint() {
  }
}

複製程式碼

通過以上定義,可以看出,RenderObject 的主要作用就是繪製和佈局的。

那麼這個 RenderObject 是哪裡來的呢?是在 Widget 裡面建立的。如上面的 Opacity 中重寫了 createRenderObject 方法來建立 RenderOpacity 物件。

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

在 RenderOpacity 內部實現了佈局、點選檢測和大小計算等功能。

三、 Element

Widget、RenderObject 與 Element

  • Element 可以理解為是其關聯的 Widget 的例項,並且關聯在 Widget 樹的特定位置上。
  • 由於 Widget 是不可變的,因此一個 Widget 可以同時用來配置多個子 Widget 樹,而 Element 就用來代表特定位置的 Widget 。
  • Widget 是不可變的,但是 Element 是可變的。
  • 一些 Element 只能有一個子節點,如 Container,Opacity,Center 還有一些可以有多個子節點,如 Column ,Row 和 ListView 等。

Element 的 生命週期:

  • Flutter framework 通過 Widget.createElement 來建立一個 element 。

  • 每當 Widget 建立並插入到 Widget 樹中時,framework 就會通過 mount 方法來把這個 widget 建立並關聯的 element 插入到 element 樹中(其父 element 會給出一個位置)。

  • 通過 attachRenderObject 方法來將 render objects 來關聯到 render 樹上,這時可以認為這個 widget 已經顯示在螢幕上了。

  • 每當執行了 rebuid 方法,widget 代表的配置資訊改變時(建立了一個新的 widget),framewrok 就會呼叫這個新的 widget 的 update 方法(新的 widget 的 和老的 widget 有相同的 runtimeType 和 key,如果不同,就要先 unmounting 然後重新裝載 widget)。

  • 當 element 的祖先想要移除一個子 element 時,可以通過 deactivateChild 方法,先把這個 element 從 樹中移除,然後將這個 element 加入到一個“不活躍元素列表”中,接著 framework 就會將這個 element 從螢幕移除(當下一個渲染幀到來這個 element 依然不活躍)。

由於 是在 Widget 中建立了Element,類似於 Widget 的繼承關係,Element 的繼承關係如下:

SingleChildRenderObjectElement → RenderObjectElement →  Element
複製程式碼

接著看一下 Opacity 中,如何建立一個 Element 。 Opacity 繼承自 SingleChildRenderObjectElement,在 SingleChildRenderObject 中建立了 Element

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

複製程式碼

在 RenderObjectElement 中提供了 mount 方法

abstract class RenderObjectElement extends Element {
  RenderObjectElement(RenderObjectWidget widget) : super(widget);
  
  RenderObject _renderObject;

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

複製程式碼

通過上面的程式碼,我們能夠發現,Element 中通過 widget.createRenderObject 方法也拿到了 RenderObject 物件,因此 Element 其實是同時包含 RenderObject 和 Widget 。

mount 方法會將 element 插入到 element 樹中,mount 中還會呼叫 attachRenderObject 方法。

abstract class RenderObjectElement extends Element {
  	@override
    void attachRenderObject(dynamic newSlot) {
      _slot = newSlot;
      _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
      _ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
      if (parentDataElement != null)
      _updateParentData(parentDataElement.widget);
    }
}


複製程式碼

在這個方法裡,通過 _findAncestorRenderObjectElement 方法, 找到了Element樹上的祖先Element,如果祖先不為空,就呼叫insertChildRenderObject方法,這個方法的意思就是把renderObject的child替換成newSlot,然後通過 _updateParentData 用於更新佈局資料的一些資訊。

總結

上面只是簡單介紹了一下 Flutter 中的 Widget 、RenderObject 和 Element 中的概念,而 Widget,Element和RenderObject體系是Flutter框架的核心 至於內部原理以及如果工作的,需要結合 Flutter 框架結構執行原理來看,這樣才能更好的理解這些概念。

參考:

Flutter的原理及美團的實踐

Flutter, what are Widgets, RenderObjects and Elements

深入瞭解Flutter介面開發


歡迎關注「Flutter 程式設計開發」微信公眾號 。

Widget、RenderObject 與 Element

相關文章