Widget、RenderObject 與 Element

Widget、RenderObject 與 Element

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

1、 Widget

在 Flutter 中,萬物皆是 Widget,不管是可見的仍是功能型的,那麼 Widget 到底是什麼呢?bash

按照慣例,先看官方文檔。微信

  • 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 方法。ide

以 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 都會建立新的對象實例的緣由。ui

2、RenderObject

  • RenderObject 是作爲渲染樹中的對象存在的。
  • RenderObject 不會定義約束關係,也就是不會對子控件的佈局位置、大小等進行管理。
  • RenderObject 中有一個 parentData 屬性,這個屬性用來保存其孩子節點的特定信息,如子節點位置,這個屬性對其孩子是透明的。

RenderObject 的定義以下this

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 內部實現了佈局、點擊檢測和大小計算等功能。

3、 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 編程開發」微信公衆號 。

相關文章
相關標籤/搜索