Flutter中的三棵树是如何完成绘制的

cubegao 2021-09-29 PM 675℃ 0条

定义

Widget:存放渲染内容、它只是一个配置数据结构,创建是非常轻量的,在页面刷新的过程中随时会重建。

其中Widget根据功能可以分为三类:

  • 组合类StatelessWidget/StatefulWidget:比如我们常见的Container就是一个组合类的控件,它主要负责组合封装多个其他负责绘制的原子组件。
  • 代理类inheritedwidget:它的父类是ProxyWidget,顾名思义。简单来说,InheritedWidget 的作用是向它的子 Widget 有效地传播和分享数据,当 InheritedWidget 作为一个Parent Widget时,它下面的Widget tree的所有Widget都可以去和 InheritedWidget 发生数据传递和交互。当数据发生改变时,一部分控件需要 rebuild,另外的控件不需要 rebuild 的时候,可以使用 InheritedWidget
  • 绘制类RenderObjectWidgetRenderObjectWidget会负责创建出负责实际渲染的RenderObject对象,RenderObject负责实际的layout()paint()。后期有空,我会手动写一个RenderObject

Element: 是分离 WidgetTree 和真正的渲染对象的中间层,可以看成flutter的骨架。 Widget 用来描述对应的Element 属性,同时持有WidgetRenderObject,存放上下文信息,通过它来遍历视图树,支撑UI结构。

其中Element可以根据功能分成两大类:

  • 组合类ComponentElement:主要包括如下 StatelessElement / StatefulElement / ProxyElement 子类;其中各 Element 都是与 Widget 对应的。
  • 绘制类RenderObjectElementRenderObjectElement对应的是RenderObjectWidget

RenderObject用于应用界面的布局和绘制,负责真正的渲染,保存了元素的大小,布局等信息。

当应用启动时 Flutter 会遍历并创建所有的 Widget 形成 Widget树,通过调用 Widget 上的 createElement() 方法创建每个 Element 对象,形成 Element 树。最后调用 ElementcreateRenderObject() 方法创建每个渲染对象,形成一个 RenderObject 树。

Widget => Element

如何通过Widget更新Element:

  • Element/ComponentElement
    单个Element时候通过updateChild方法更新;
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
  //如果newWidget是null,并且old child非null,直接deactivateChild
  if (newWidget == null) {
    if (child != null)
      deactivateChild(child);
    return null;
  }
  final Element newChild;
  if (child != null) {
    //新旧widget相同的情况
    if (child.widget == newWidget) {
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
      newChild = child;
    } else if (Widget.canUpdate(child.widget, newWidget)) {
      //可以update的情况,也就是runtimetype和key相同
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
      child.update(newWidget);
      newChild = child;
    } else {
      //其他情况,移除旧的,重新inflateWidget新的widget,会创建element
      deactivateChild(child);
      newChild = inflateWidget(newWidget, newSlot);
    }
  } else {
    //old child是null,这里直接inflate新的widget,会创建element
    newChild = inflateWidget(newWidget, newSlot);
  }
  return newChild;
}

大致归纳一下,如下表:

标题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].
  • RenderObjectElement:
    多个Element时候通过updateChildren方法更新;
List<Element> updateChildren(List<Element> oldChildren, List<Widget> newWidgets, { Set<Element>? forgottenChildren }) {
    //如果forgottenChildren包含当前child,表示它已经在oldChild中,返回null
    Element? replaceWithNullIfForgotten(Element child) {
      return forgottenChildren != null && forgottenChildren.contains(child) ? null : child;
    }

    int newChildrenTop = 0;
    int oldChildrenTop = 0;
    int newChildrenBottom = newWidgets.length - 1;
    int oldChildrenBottom = oldChildren.length - 1;

    final List<Element> newChildren = oldChildren.length == newWidgets.length ?
        oldChildren : List<Element>.filled(newWidgets.length, _NullElement.instance, growable: false);

    Element? previousChild;

    // 从列表的顶部向下更新Element
    while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
      final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
      final Widget newWidget = newWidgets[newChildrenTop];
      if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
        break;
      final Element newChild = updateChild(oldChild, newWidget, IndexedSlot<Element?>(newChildrenTop, previousChild))!;
      newChildren[newChildrenTop] = newChild;
      previousChild = newChild;
      newChildrenTop += 1;
      oldChildrenTop += 1;
    }

    // 从列表底部开始扫描Element,但不更新
    while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
      final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]);
      final Widget newWidget = newWidgets[newChildrenBottom];
      assert(oldChild == null || oldChild._lifecycleState == _ElementLifecycle.active);
      if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
        break;
      oldChildrenBottom -= 1;
      newChildrenBottom -= 1;
    }

    // 扫描旧的子Element列表里面中间的子Element,保存Widget有Key的Element到oldKeyChildren,其他的失效
    final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
    Map<Key, Element>? oldKeyedChildren;
    if (haveOldChildren) {
      oldKeyedChildren = <Key, Element>{};
      while (oldChildrenTop <= oldChildrenBottom) {
        final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]);
        if (oldChild != null) {
          if (oldChild.widget.key != null)
            oldKeyedChildren[oldChild.widget.key!] = oldChild;
          else
            deactivateChild(oldChild);
        }
        oldChildrenTop += 1;
      }
    }

    // 根据Widget的Key更新oldKeyChildren中的Element。
    while (newChildrenTop <= newChildrenBottom) {
      Element? oldChild;
      final Widget newWidget = newWidgets[newChildrenTop];
      if (haveOldChildren) {
        final Key? key = newWidget.key;
        if (key != null) {
          oldChild = oldKeyedChildren![key];
          if (oldChild != null) {
            if (Widget.canUpdate(oldChild.widget, newWidget)) {
              // we found a match!
              // remove it from oldKeyedChildren so we don't unsync it later
              oldKeyedChildren.remove(key);
            } else {
              // Not a match, let's pretend we didn't see it for now.
              oldChild = null;
            }
          }
        }
      }
      final Element newChild = updateChild(oldChild, newWidget, IndexedSlot<Element?>(newChildrenTop, previousChild))!;
      newChildren[newChildrenTop] = newChild;
      previousChild = newChild;
      newChildrenTop += 1;
    }

    // 整个列表扫描结束
    newChildrenBottom = newWidgets.length - 1;
    oldChildrenBottom = oldChildren.length - 1;

    // 更新底部的Element
    while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
      final Element oldChild = oldChildren[oldChildrenTop];
      final Widget newWidget = newWidgets[newChildrenTop];
      final Element newChild = updateChild(oldChild, newWidget, IndexedSlot<Element?>(newChildrenTop, previousChild))!;
      newChildren[newChildrenTop] = newChild;
      previousChild = newChild;
      newChildrenTop += 1;
      oldChildrenTop += 1;
    }

    // 清除旧子Element列表中其他所有剩余Element
    if (haveOldChildren && oldKeyedChildren!.isNotEmpty) {
      for (final Element oldChild in oldKeyedChildren.values) {
        if (forgottenChildren == null || !forgottenChildren.contains(oldChild))
          deactivateChild(oldChild);
      }
    }
    return newChildren;
  }

该函数的主要职责如下:

  • 复用能复用的子节点,并调用updateChild对子节点进行更新。
  • 对不能更新的子节点,调用deactivateChild对该子节点进行失效。

我们注意到updateChild如果old child是空或者无法update就需要inflateWidget;
我们从源码看看创建方法inflateWidget():

Element inflateWidget(Widget newWidget, Object? newSlot) {
  final Key? key = newWidget.key;
  if (key is GlobalKey) {
    final Element? newChild = _retakeInactiveElement(key, newWidget);
    if (newChild != null) {
      newChild._activateWithParent(this, newSlot);
      final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
      return updatedChild!;
    }
  }
  //创建子Element
  final Element newChild = newWidget.createElement();
  //将child挂载到当前element
  newChild.mount(this, newSlot);
  //返回child element
  return newChild;
}

逻辑非常简单,创建一个element,然后mount到当前element

RenderObject

RenderObjectWidget会调用createElement,创建出RenderObjectElement
然后RenderObjectWidget会调用createRenderObject,创建出RenderObject
然后通过[RenderObjectElement.mount]方法,将RenderObject挂载为RenderObjectElement._renderObject属性。从此可以看出,Element是连接WidgetRenderObject的桥梁。

标签: Flutter

非特殊说明,本博所有文章均为博主原创。

上一篇 如何在ListView中优雅的嵌套ListView
下一篇 没有了

评论啦~