美文网首页
Widget、Element和RenderObject之间的转化

Widget、Element和RenderObject之间的转化

作者: 维特or卡顿 | 来源:发表于2020-09-08 14:20 被阅读0次

    开篇

    image

    Flutter中页面的渲染渲染离不开三个重要的元素:Widget、Element、RenderObject。是一个从Widget到Element再到RenderObject的过程。而具体到源码中,这个转换的工作时如何实现的呢,今天我们就来跟着源码简略分析一下。为了更流程的分析它们三者之间的关系,我们先根据Framework层源码的注释了解一下它们,然后再从Flutter项目入口进行分析。

    Widget、Element和RenderObject

    根据源码注释我么可以大致知道:

    1. Widget是对UI的一种廉价的、不可变的,描述(如何配置子树)Element的配置,并可以生成Element
    2. Widget本身没有状态,所有属性都是final,Element持有WidgetRenderObject的实例
    3. 给定Widget可不断加入到树中不同的位置,并生成Element作为它在树中的实例,它们是一对多的关系
    4. RenderObject是实际的渲染对象,Element就可以理解为将变化的Widget转化为较为稳定的RenderObject

    Flutter工程的入口

    新建一个Flutter项目后,我们会看到其工程目录如下

    image
    其中,main.dart文件中有main函数作为程序的入口:
    void main() {
      runApp(MyApp());
    }
    

    其中MyApp()就是一个Widget,我们忽略其他代码,只关心它在代码中的走向,发现它最终作为一个child用于RenderObjectToWidgetAdapter的初始化,并且通过attachToRenderTree方法生成了一个Element

      void attachRootWidget(Widget rootWidget) {
        _readyToProduceFrames = true;
        _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
          container: renderView,
          debugShortDescription: '[root]',
          child: rootWidget,
        ).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>);
      }  
    

    继续看它的attachToRenderTree方法:

     RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
        if (element == null) {
          owner.lockState(() {
            element = createElement();
            assert(element != null);
            element.assignOwner(owner);
          });
          owner.buildScope(element, () {
            element.mount(null, null);
          });
          // This is most likely the first time the framework is ready to produce
          // a frame. Ensure that we are asked for one.
          SchedulerBinding.instance.ensureVisualUpdate();
        } else {
          element._newWidget = this;
          element.markNeedsBuild();
        }
        return element;
      }
      RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);
    

    可以发现:在我们创建的“根”Widget之前,会有一个RenderObjectToWidgetAdapter作为真正的根Widget,而与之对应的是一个RenderObjectToWidgetElement作为根Element。通过调用createElement方法初始化与之对应的Element。该方法在抽象类Widget中被定义,可被子类重写。
    而观察Element的初始化方式以及定义:

    Element(Widget widget)
        : assert(widget != null),
          _widget = widget;
    

    不难发现:Element初始化时接受了当前的Widget并作为私有变量持有。那么我们的Widget,MyApp是在何时转换成Element的呢?

    自上而下的调用创建Element

    继续追踪attachToRenderTree方法源码,我们发现在Element被初始化后,会调用element.mount(null, null)方法(只保留关键代码),该代码在framework.dart文件中的Element类中。

    void mount(Element parent, dynamic newSlot) {
        assert(parent == null);
        super.mount(parent, newSlot);
        _rebuild();
      }
    void _rebuild() {
        try {
          //_child是其子Element,也就是`MyApp`将要生成的Element,此时进行初始化
          //widget.child是我们的`MyApp`
          _child = updateChild(_child, widget.child, _rootChildSlot);
          assert(_child != null);
        } catch (exception, stack) {
          ...
        }
      }
    Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
        if (newWidget == null) {
           //如果newWidget为空,说明没有子树,返回null
          if (child != null)
            deactivateChild(child);
          return null;
        }
        Element newChild;
        //此时_child为null
        if (child != null) {
          ...
        } else {
          //初始化
          newChild = inflateWidget(newWidget, newSlot);
        }
        return newChild;
      }
      
    Element inflateWidget(Widget newWidget, dynamic newSlot) {
        final Key key = newWidget.key;
        if (key is GlobalKey) {
          final Element newChild = _retakeInactiveElement(key, newWidget);
          if (newChild != null) {
            assert(newChild._parent == null);
            assert(() {
              _debugCheckForCycles(newChild);
              return true;
            }());
            newChild._activateWithParent(this, newSlot);
            final Element updatedChild = updateChild(newChild, newWidget, newSlot);
            assert(newChild == updatedChild);
            return updatedChild;
          }
        }
        //调用`MyApp`的`createElement`方法进行初始化
        final Element newChild = newWidget.createElement();
        assert(() {
          _debugCheckForCycles(newChild);
          return true;
        }());
        //执行monut方法,继续下一个Widget的转换
        newChild.mount(this, newSlot);
        assert(newChild._debugLifecycleState == _ElementLifecycle.active);
        return newChild;
      }
    

    可以发现,当我们的程序启动时,runApp会创建一个根Widget并调用其createElement方法生成element。并执行element的mount方法。之后,element会读取并判断自己所持有的Widget对象是否有子Widget,就是上面代码中widget.child。当发现child不为空,就会继续执行createElement()方法,并执行mount()方法进入下一次子树。依次类推直至遍历完所有子树。

    依旧是自上而下的调用却有点不一样

    在追踪上述代码时,我们并没有直接发现类似createElementcreateRenderObject()方法被调用,我们观察RenderObjectToWidgetElement的继承关系:

    image
    最终可在RenderObjectElementmount()方法里看到这样的代码:
    @override
      void mount(Element parent, dynamic newSlot) {
        super.mount(parent, newSlot);
        ....
        _renderObject = widget.createRenderObject(this);
        ....
      }
    

    在这里,我们发现了初始化RenderObject的地方。可以发现,RenderObjectWidget实在Element里被初始化,并且和Widget一样被Element所持有。并且,采取和Element一样,自上而下的不断遍历整个树进行初始化。
    但是有一点我们需要注意:
    createElement()方法在所有widget的基类Widget中被定义了。但是createRenderObject()方法是在Widget的子类RenderObjectWidget中被定义的,于此同时,createRenderObject()方法也只会在RenderObjectElement中被调用。
    这说明:每个Widget都会生成一个Element,但并不是每个Element都会创建一个RenderObject,只有RenderObjectWidget类型的子类的Widget创建的继承自RenderObjectElement类型的element才会创建RenderObject。也就是说,widget树、element树和RenderObject树并不是一是一对应的。

    image

    总结

    本文只是Flutter UI相关的冰山一角,并没有深入了解Flutter的绘制原理。旨在通过源码了解Widget、Element和RenderObject直接的关系,理清他们之间的逻辑。

    相关文章

      网友评论

          本文标题:Widget、Element和RenderObject之间的转化

          本文链接:https://www.haomeiwen.com/subject/yvndektx.html