【Flutter 專題】101 何為 Flutter Elements ?

阿策小和尚發表於2021-07-08

      小菜前段時間簡單瞭解了一下 Widget 的相關知識,其中 Widgetimmutable 不可變的,而 Widget 是如何做到更新重繪的,這就離不開 ElementRenderObject;小菜簡單瞭解一下 Element 的相關小知識;

Element

      ElementWidgetUI 樹具體位置的一個例項化物件;UI ViewElement 層級結構中會構建一個真實的 Element Tree,是真正的檢視樹結構;Element 作為 WidgetRenderObject 之間的協調者,並根據 Widget 的變化來完成結點的增刪改的操作;

原始碼分析

      Element 所涉及原始碼較長,小菜僅針對具體的方法和生命週期進行學習;

生命週期

enum _ElementLifecycle {
  initial,
  active,
  inactive,
  defunct,
}
複製程式碼

      Element 的生命週期主要包括如下幾分,分別是 initial 初始化,active 活躍狀態,inactive 不活躍狀態以及 defunct 失效狀態;

針對方法

1. createElement
Element(Widget widget) : assert(widget != null), _widget = widget;
複製程式碼

      建立一個使用指定 Widget 作為其配置的 Element;通過 Widget 呼叫 Widget.createElement 來建立 Element,作為 Element 的初始位置;

2. mount
@mustCallSuper
void mount(Element parent, dynamic newSlot) {
    ...
    _parent = parent;
    _slot = newSlot;
    _depth = _parent != null ? _parent.depth + 1 : 1;
    _active = true;
    if (parent != null) 
      _owner = parent.owner;
    if (widget.key is GlobalKey) {
      final GlobalKey key = widget.key;
      key._register(this);
    }
    _updateInheritance();
}
複製程式碼

      mount() 會將新建立的 Element 新增到指定的父級 slot 插槽樹中,通過呼叫 attachRenderObject 新增到渲染樹上;

3. update
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    if (newWidget == null) {
      if (child != null) deactivateChild(child);
      return null;
    }
    if (child != null) {
      if (child.widget == newWidget) {
        if (child.slot != newSlot) updateSlotForChild(child, newSlot);
        return child;
      }
      if (Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot) updateSlotForChild(child, newSlot);
        child.update(newWidget);
        return child;
      }
      deactivateChild(child);
      assert(child._parent == null);
    }
    return inflateWidget(newWidget, newSlot);
}
複製程式碼

      updateChildElement 的核心方法,每當需要增加,修改,刪除子 child 時都會呼叫;主要根據 Widget 的變化用於 Element 的更新,進而更新 UI 樹;

newWidget == nullnewWidget != null
child == nullReturns null.Returns new [Element].
child != nullOld child is removed, returns null.Old child updated if possible, returns child or new [Element].
  1. 當更新後的 Widgetnull 時,對應的子節點已經移除,如果當前 child 不為 null,則直接 remove 掉;
  2. 當更新後的 Widget 不為 null 且當前 childnull 時,說明新 Widget 是新建立的,則 inflateWidget 建立子節點;
  3. 當更新後的 Widget 不為 null 且當前 child 也不為 null 該節點存在時,若 child.widget == newWidget 說明子節點前後未發生變化,若 child.slot != newSlot 說明子節點在兄弟結點間移動了位置,此時 updateSlotForChild 更新節點位置;否則直接返回子節點;
  4. 當更新後的 Widget 不為 null 且當前 child 也不為 null 該節點存在時,若 Widget.canUpdatetrue 說明可以用 newWidget 修改子節點,直接呼叫 update 更新即可;否則先將子節點移除再通過 newWidget 建立新的子節點;其中 canUpdate 主要是判斷新舊 WidgetkeyruntimeType 是否一致;
4. deactivate
@protected
void deactivateChild(Element child) {
    child._parent = null;
    child.detachRenderObject();
    owner._inactiveElements.add(child); // this eventually calls child.deactivate()
}
複製程式碼

      deactivateChild 將指定 Element 已到非活動 Element 列表中,並將渲染物件從渲染樹中移除;該方法可以阻止 Element 成為其子類;

5. activate
@mustCallSuper
void activate() {
    final bool hadDependencies = (_dependencies != null && _dependencies.isNotEmpty) || _hadUnsatisfiedDependencies;
    _active = true;
    _dependencies?.clear();
    _hadUnsatisfiedDependencies = false;
    _updateInheritance();
    
    if (_dirty)
      owner.scheduleBuildFor(this);
    if (hadDependencies)
      didChangeDependencies();
}
複製程式碼

      activate() 為將 Element 重新合併到樹上時,框架會從 inactive 非活躍 Element 列表中刪除該元素,且該元素呼叫 activate 並將 Element 的渲染物件新增到渲染樹上;

6. unmount
@mustCallSuper
void unmount() {
    if (widget.key is GlobalKey) {
        final GlobalKey key = widget.key;
        key._unregister(this);
    }
}
複製程式碼

      unmount() 為當框架永遠不會重新啟用時呼叫;為了避免在一次動畫執行過程中反覆建立,移除特定 Element 時,非活躍狀態的 Element 都會在當前動畫過程最後一幀先保留,如果到動畫結束後還未變成活躍狀態,則呼叫 unmount() 將該 Element 徹底移除;

對應關係

  1. Widget.createElementinitial 從無到有的初始化生命週期;
  2. mountinitial 初始化狀態到 active 活躍狀態到生命週期過渡;
  3. update 只有在 active 活躍狀態時才會呼叫;
  4. deactivateactive 活躍狀態到 inactive 非活躍狀態生命週期過渡;
  5. activateinactive 非活躍狀態到 active 活躍狀態的生命週期過渡;
  6. unmountinactive 非活動狀態到 defunct 失效狀態生命週期的過渡;

子類 Element

      Element 主要有組合類 ComponentElement 和渲染類 RenderObjectElement 兩個子類;

ComponentElement

      ComponentElement 為組合類 Element,主要包括如下 StatelessElement / StatefulElement / ProxyElement 子類;其中各 Element 都是與 Widget 對應的;

StatelessElement
class StatelessElement extends ComponentElement {
  StatelessElement(StatelessWidget widget) : super(widget);

  @override
  StatelessWidget get widget => super.widget;

  @override
  Widget build() => widget.build(this);

  @override
  void update(StatelessWidget newWidget) {
    super.update(newWidget);
    assert(widget == newWidget);
    _dirty = true;
    rebuild();
  }
}
複製程式碼

      StatelessElement 相對比較簡單,主要是重寫 update() 在需要發生變更時 rebuild() 即可;

StatefulElement
@override
void update(StatefulWidget newWidget) {
    super.update(newWidget);
    final StatefulWidget oldWidget = _state._widget;
    _dirty = true;
    _state._widget = widget;
    try {
      _debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
      final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;
    } finally {
      _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
    }
    rebuild();
}
複製程式碼

      StatefulElement 是對應 StatefulWidget 的,包括完整的 Element 生命週期,其中在更新時會更新 Staterebuild()

ProxyElement
abstract class ProxyElement extends ComponentElement 
  ProxyElement(ProxyWidget widget) : super(widget);

  @override
  ProxyWidget get widget => super.widget;

  @override
  Widget build() => widget.child;

  @override
  void update(ProxyWidget newWidget) {
    final ProxyWidget oldWidget = widget;
    assert(widget != null);
    assert(widget != newWidget);
    super.update(newWidget);
    assert(widget == newWidget);
    updated(oldWidget);
    _dirty = true;
    rebuild();
  }

  @protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }

  @protected
  void notifyClients(covariant ProxyWidget oldWidget);
}
複製程式碼

      ProxyElement 作為一個抽象類,其子類是 ParentDataElementInheritedElement;當 Widget 更新時呼叫 update()notifyClients() 用於新舊 Widget 確實已改變時呼叫;

RenderObjectElement

      RenderObjectElement 為渲染型別 Element 對應的是 RenderObjectWidgetRenderObjectElement 作為抽象類也繼承了 Element 所有的生命週期方法;

      大多數的 RenderObjectElement 都只對應一個 RenderObject 即只有一個子節點,例如 RootRenderObjectElement / SingleChildRenderObjectElement;但也有特殊的,如 LeafRenderObjectElement 子類沒有子節點,以及 MultiChildRenderObjectElement 子類可以有多個子節;


      Element 作為 WidgetRenderObject 的協作者起到了承上啟下的左右;小菜對會在下一篇簡單學習 RenderObject;小菜對原始碼的理解還不夠深入,如有錯誤,請多多指導!

來源:阿策小和尚

相關文章