Flutter深入學習:Widget/Element/RenderObject詳解

小碼線上炒飯發表於2021-05-26

前言

Flutter知識體系中,Widget、Element以及RenderObject是比較重要的三個類族;裡面的知識點十分多,今天側重於給大家分享這三棵樹是如何構建出來的。

類族繼承關係

只列舉一些和本文有關的

Widget:

截圖2021-05-26 下午3.53.14.png 通過上面的圖可以知道,Widget基類定義了createElement()函式,子類通過override該方法構造不同的element類;並且還需要重點關注的是,只有RenderObjectWidget類才定義createRenderObject()函式,所以,能構造出renderObject的都只能是其子類;其次,可以發現只有StatelessWidget類才有build()函式(StatefulWidget會通過state呼叫build()),這個在後面會說到。

Element:

截圖2021-05-26 下午4.05.11.png 從這裡可以看到,element和widget的各個類都基本是對應的

RenderObject:

截圖2021-05-26 下午4.19.17.png

Widget/Element/RenderObject的初始化過程,以及各自的關係

首先定位到main.dart的入口函式:

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}
複製程式碼

WidgetsFlutterBinding主要負責連線Flutter框架層和engine層,這裡通過我們傳入的Widget生成Wiget/Elemtn/RenderObject樹,其他關於WidgetsFlutterBinding這裡就不過多去討論,在這我們只需要知道該類有兩個重要的屬性,分別是Render Tree的根節點,Element Tree的根節點;以及初初始化根節點的方法:

RenderObjectToWidgetAdapter _renderViewElement // 繼承自RenderObjectWidget
RenderView renderView // 繼承自RenderObject

// 初始化rootElement和rootWidget,並把rootRenderObject繫結到rootElement上,把我們寫的Widget(app)繫結到rootWidget上
void attachRootWidget(Widget rootWidget) {
    _readyToProduceFrames = true;
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    ).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>);
  }
複製程式碼

接下來,我們來看一下class RenderObjectToWidgetAdapter的定義:

class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
    final Widget child;
    final RenderObjectWithChildMixin<T> container;
    RenderObjectWidget {
        RenderObjectToWidgetAdapter({
        this.child,
        this.container,
        this.debugShortDescription,
      }) : super(key: GlobalObjectKey(container));
      
    // 把自己作為element建構函式的引數,生成一個element例項
    @override
    RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);

    @override
    RenderObjectWithChildMixin<T> createRenderObject(BuildContext context) => container;
    
    RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element 
    if (element == null) {
      ...
      // 沒有rootElement就例項化一個
      element = createElement();
      // 通過mount函式,例項化一個rootRenderObject並掛載到rootElement上
      element.mount(null, null);
      ...
    } else {
      ...
    }
    // 返回rootElement給外部WidgetsFlutterBinding的_renderViewElement賦值
    return element;
  }
}
複製程式碼

通過上面的程式碼可以知道,RenderObjectToWidgetAdapter類就是一個介面卡,通過它構造出了整個樹

我們先來看一下根節點是如何初始化生成的,簡要如下:
  1. RenderObjectToWidgetAdapter通過建構函式獲取了外部傳入的widget和renderObject(rootRenderObject),初始化一個rootWidget的例項物件,並把外部傳入的widget掛載到rootWidget上
  2. RenderObjectToWidgetAdapter的例項物件通過createElement()建立一個RenderObjectToWidgetElement的例項物件作為rootElement,rootElement持有rootWidget和rootRenderObject.
  3. rootElement的呼叫mount()函式,在父類的實現中,會通過一個getter方法,獲取當前rootElement持有的widget,也就是rootWidget,然後rootWidget呼叫createRenderObject()方法返回從外部獲取的renderObject,並掛載到rootElement,作為rootRenderObject
  4. 通過上面的步驟,三棵樹的根節點就建立好,並建立了相互的引用關係
子節點是如何初始化生成的,簡要如下:
  1. rootElement在mount()函式最後,繼續呼叫了自己的私有函式_rebuild(),通過原始碼可以看到,rootElement的_child就被構造完成。
void _rebuild() {
    try {
      _child = updateChild(_child, widget.child, _rootChildSlot);
      assert(_child != null);
    } catch (exception, stack) {
      ...
      final Widget error = ErrorWidget.builder(details);
      _child = updateChild(null, error, _rootChildSlot);
    }
  }
複製程式碼
  1. 這裡的updateChild()函式是一個重點,看其原始碼可以知道,這個函式主要是通過引數child和newWidget的組合判斷返回結果,如下:
newWidget == nullnewWidget != null
child == nullReturn:null.Return new [Element]
child != null移除child,Return:nullreturn child or new [Element]

由於這裡我們只討論第一次初始化,所以這裡傳入的child == null, newWidget是已掛載到rootWidget上的widget,所以會通過inflateWidget()函式初始化一個newChild,並返回掛載到rootElement上。注意,這裡的newChild初始化後,並不是直接返回的,而是繼續呼叫其mount()函式,通過super.mount(),這裡需要注意的是,ComponentElement和RenderObjectElement是不一樣的,RenderObjectElement會初始化一個RenderObject掛載到Element上。整個過程就是這樣不斷的查詢widget的child構造出相對應的element和renderObject.

  Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    ...
    Element newChild;
    if (child != null) {
      ...
    } else {
      newChild = inflateWidget(newWidget, newSlot);
    }
    ...
    return newChild;
  }
  
  @protected
  Element inflateWidget(Widget newWidget, dynamic newSlot) {
    ...
    final Element newChild = newWidget.createElement();
    ...
    newChild.mount(this, newSlot);
    ...
    return newChild;
  }
複製程式碼

結束

腦袋嗡嗡的... 文章篇幅有限,到這裡就結束了,下次再繼續聊Flutter. 有錯誤的地方,請各位大佬不吝指正,共同學習,一起提高。

相關文章