阅读 291

Flutter深入学习:Widget/Element/RenderObject详解

前言

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. 有错误的地方,请各位大佬不吝指正,共同学习,一起提高。

文章分类
前端
文章标签