从源码看Flutter三棵树渲染机制与原理


Flutter 是一个优秀的 UI 框架,借助它开箱即可用的 Widgets 我们能够快速搭建出漂亮和高性能的用户界面。那么那些 Widget 底层又是如何完成渲染的呢?

什么是三棵树

Flutter 中和 Widgets 一起协同工作的还有另外两个伙伴: ElementsRenderObjects,由于都有着树形结构,我们常称为三棵树。

Widget树

WidgetFlutter 的核心部分,是用户界面的不可变描述,widget 的功能也就是描述一个 UI 元素的配置数据,也就是说 Widget 并不是最终绘制到屏幕上的元素,它只是描述显示元素的一个配置而已。

事实上再代码运行的过程中并没有明确的 widget 树的概念,这棵树是我们在开发过程中对 widget 嵌套的描述,因为确实长得像一棵树所以才这样说。

abstract class Widget {
  const Widget({ this.keuy });
  final Key key;
  
  /// createElement 是抽象方法,子类必须实现,该方法创建一个 Element,所以每个 Element 都会对应一个 widget 对象
  Element creatElement();
  
  /// 判断 oldWidget 和 newWidget 是不是同一个 widget, 如果 runtimeType 和 key 相同则认为是同一个 widget
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key;
  }
}

需要注意的是 widget 不能被修改,如果修改只能重新创建,因为 widget 并不参与渲染,它只是一个配置文件而已,只需要告诉渲染层自己的样式即可。

Elements树

Element 是分离 WidgetTree 和真正的渲染对象的中间层,Elements 是实例化的 Widget 对象,通过 WidgetcreateElement() 方法,在特定位置使用 Widget 配置数据生成,并且 widget 可以对应多个 Element。这是因为同一个 Widget 可以被添加到 Element 树的不同部分。而真正渲染的时候,每一个 Element 都会对应一个 Widget 对象。

所谓的 UI 树就是有一个个 Element 节点构成。组件的最终 Layout,渲染都是通过 RenderObject 来完成,从创建到渲染的大致流程是:根据 Widget 生成 Element,然后再创建对应的 RenderObject 并关联到 Element.renderObject 属性上,最后通过 RenderObject 来完成布局排列和绘制。

如上说过,Element 表示一个 Widget 树中特定的实例,大多数 Element 只有唯一的 RenderObject,但是还有一些 Element 会有多个子节点,如集成自 RenderObjectElement 一些类,比如 MultiChildRenderObjectObject。最终所有的 ElementRenderObject 构成一棵树,我们称之为渲染树。

简单来讲,我们可以认为 Flutter 的 UI 系统包含了三棵树: Widget 树,Element 树,Render 树,他们的对应关系是 Element 树是根据 Widget 树生成,而 Render 树又依赖 Element 树,关系如下:
请添加图片描述

Element 类源码

abstract class Element extends DiagnosticableTree implements BuildContext {
  Element(Widget widget)
    : assert(widget != null),
  _widget = widget;
  Element _parent;
  
  
  Widget get widget => _widget;
  Widget _widget;
  
  RenderObject get renderObject {...}
  
  
  void mount(Element parent, dynamic newSlot){...}
  
  
  void activate(){...}
  
  
  void deactivate(){...}
  
  
  void unmount(){...}
}

Render树

用于应用界面布局和绘制,负责真正的渲染,保存了元素大小,布局信息;另外实例化一个 RenderObject 是非常消耗性能的,下面会有详细的介绍。

三棵树如何运行

简单了解了三棵树之后,那 Flutter 是如何创建和布局的呢?以及三棵树之间他们是如何协同的呢?可以通过一个示例来看:

class ThreeTree extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Contariner(
      color: Colors.green,
      child: Container(
      	color: Colors.red
      )
    )
  }
}

上面这个简单的例子,他由三个 Widget 组成, ThreeTreeContainerContainer。那么当 FlutterrunApp()方法被调用时候会发生什么呢?

runAPP()被调用时候,第一时间底层会发生下面时间:

  • Flutter 会构建包含这三个 WidgetWidget 树;
  • Flttter 遍历 Widget 树,然后根据其中 Widget 调用 creatElements()来创建对象,最后将这些对象组建成 Element 树;
  • 接下来会创建第三个树,这个树包含了与 Widget 对应的 Element 通过 creatRenderObject()创建的 RenederObject;

下图是 Flutter经过这三个步骤后的状态:
请添加图片描述
从上图中可以看出,Flutter 创建了三个不同的树,一个对应 Wideget ,一个对应 Element,一个对应 RenderObject。每一个 Element 中都有着对应的 WidgetRenderObject 的引用。可以说 Element 是存在于可变 Widget 树和不可变 RenderObject 树之间的桥梁。在 Flutter 里面 Element 擅长比较 WidgetRenderObject 两个 Object,它的作用是配置好 Widget 在树中的位置,并且保持相对应的 RenderObjectWidget 之间的引用。

三棵树的作用

简单来说就是为了性能,复用 Element 从而减少频繁创建和销毁 RenderObject。因为实例化一个 RendeObject 的成本很高,频繁的实例化和销毁 RenderObject 对性能的影响很大,所以当 Widget 树改变时候,Flutter 使用 Element 树来比较新的 Widget 树和原来的 Widget 树.

Element

Element 的生命周期
initial 初始状态

_ElementLifeCycle _lifecycleState = _ElementLifecycle.initial

active
/// RenderObjectElement 的 mount 方法

void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSolt);
  //...
  _renderObject  = widget.createRenderObject(this);
  assert(_slot == newSlot);
  attachRenderObject(newSlot);
  _dirty = false;
}

fragment 调用 element.mount方法后,mount 方法会首先调用 element 对应的 widgetcreateRenderObject方法来创建与 element 对应的 RenderObject 对象。

然后调用 element.attachRenderObjectelement.renderObject 添加到渲染树插槽的位置(这一步不是必须的,一般发生在 Element 树结构发生变化时才需要重新 attach)。

插入到渲染后的 element 就处于 active 状态, 处于 active 状态后就可以显示在屏幕上了(也可以设置属性 hide 进行隐藏)。

super.mount(parent, newslot)
_lifecycleState = _ElementLifecycle.active

当 widget 更新时,为了避免重建 element 会判断是否可以更新,会调用 updateChild 方法。

// framework.dart

	Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    /// 没有新的 widget,并且有原来的widget,则移除原来的 child,因为他不再有配置
    if(newWidget == null) {
      if(child != null) {
        deactivateChild(child);
        return null;
      }
      Element newChild;
      /// 原来有 child
      if(child != null) {
        assert((){
          final int oldElementClass = Element._debugConcreteSubtype(child);
          final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
          hasSameSuperclass  = oldElementClass == newWidgetClass;
          return true;
        }());
        /// 如果父控件类型相同,子控件也相同,直接更新
        if(hasSameSuperclass && child.widget == newWidget){
          if(child.slot != newSlot)
            updateSlotForChild(child, newSlot);
          newChild = child;
        } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
          /// 父控件类型相同,并且可以更新 widget,则更新 child 
          if(child.slot != newSlot)
            updateSlotForChild(child, newSlot);
          child.update(newWidget);
          assert(child.widget == newWidget);
          assert((){
            child.owner._debugElementWasRebuild(child);
            return true;
          }());
          newChild = child;
        } else {
          /// 不能更新,则需要先移除原有的 child,并且创建新的 child 并添加
          deactivateChild(child);
          assert(child._parent == null);
          newChild = inflateWidget(newWidget, newSlot);
        }
      } else {
        /// 没有 child,直接创建 child并添加
        newChild = inflateWidget(newWidget, newSlot);
      }
      assert((){
        if(child != null)
          _debugRemoveGlobalKeyReservation(child);
        final Key key = newWidget?.key;
        if(key is GlobalKey) {
          key._debugReserveFor(this, newChild);
        }
        return true;
      }());
      
      return newChild;
    }
    
    ...
      // 判断是否可以更新
      static bool canUpdate(Widget oldWidget, Widget newWidget) {
      return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key;
    }
  }

widget.canUpdate,主要判断 type 和 key 是否相同。如果我们需要强制更新,只需要修改key即可,官方不推荐修改 runtimeType。

static bool canUpdate(Widget oldWidget, Widget newWidget){
  return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key;
}

从“非活动”到“活动”生命周期的转换

在上面的updateChild方法中,最后如果调用了 inflateWidget()方法后,就需要将状态从 inactive 转到 active状态。


void activate() {
  assert(_lifecycleState == _ElementLifecycle.inactive);
  assert(widget != null);
  assert(owner != null);
  assert(depth != null);
  final bool hadDependencies = (_dependencies != null && _dependencies!.isNotEmpty) || _hadUnsatisfiedDependencies;
  _lifecycleState = _ElementLifecycle.active;
  // We unregistered our dependencies in deactivate, but never cleared the list.
  // Since we're going to be reused, let's clear our list now.
  _dependencies?.clear();
  _hadUnsatisfiedDependencies = false;
  _updateInheritance();
  if (_dirty)
    owner!.scheduleBuildFor(this);
  if (hadDependencies)
    didChangeDependencies();
}
inactive

从“活动”到“非活动”生命周期状态的转换

在上面updateChild方法中,我们可以看到新的 widget 为空并且存在旧的,就会调用 deactiveChild 移除 child, 然后调用 deactivate方法将_lifecycleState设置为inactive.


void deactivate() {
  assert(_lifecycleState == _ElementLifecycle.active);
  assert(_widget != null); // Use the private property to avoid a CastError during hot reload.
  assert(depth != null);
  if (_dependencies != null && _dependencies!.isNotEmpty) {
    for (final InheritedElement dependency in _dependencies!)
      dependency._dependents.remove(this);
  }
  _inheritedWidgets = null;
  _lifecycleState = _ElementLifecycle.inactive;
}
defunct

从“非活动”到“已失效”生命周期状态的转换


void unmount() {
  assert(_lifecycleState == _ElementLifecycle.inactive);
  assert(_widget != null); // Use the private property to avoid a CastError during hot reload.
  assert(depth != null);
  assert(owner != null);
  // Use the private property to avoid a CastError during hot reload.
  final Key? key = _widget!.key;
  if (key is GlobalKey) {
    owner!._unregisterGlobalKey(key, this);
  }
  // Release resources to reduce the severity of memory leaks caused by
  // defunct, but accidentally retained Elements.
  _widget = null;
  _dependencies = null;
  _lifecycleState = _ElementLifecycle.defunct;
}
StatelessElement

Container 创建出来的是 StatelessElement, 下面就从核心代码看一下它的调用过程。

class StatelessElement extends ComponentElement {
  /// 通过 createElement 创建传入的 widget
  StatelessElement(StatelessWidget widget): super(widget);
  
  
  StatelessWidget get widget => super.widget as StatelessWidget;
  
  /// 这里调用的build 就是我们自己实现的 build 方法
  Widget build() => widget.build(this);
}

abstract class ComponentElement extends Element {
  /// Creates an element that uses the given widget as its configuration.
  ComponentElement(Widget widget) : super(widget);
    Element? _child;

  bool _debugDoingBuild = false;
   
  bool get debugDoingBuild => _debugDoingBuild;
  
   
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    _firstBuild();
  }

  void _firstBuild() {
    rebuild();
  }

  
  void performRebuild() {
   //.....
  }

  
  Widget build();
}


abstract class Element extends DiagnosticableTree implements BuildContext {
  //构造方法,接受一个 widget 参数
  Element(Widget widget)
    :assert(widget != null),
     _widget = widget;
  
  
  Widget get widget => _widget;
  Widget _widget;
  
  void rebuild() {
    if(!avtive || !dirty)
      return;
    Element debugPreviousBuildTarget;
    
    /// 这里调用的 performRebuild 方法,在当前类并没有实现,只能去自己类里查找实现
    performRebuild();
  }
  
  /// Called by rebuild() after the appropriate checks have been made.
  
  void performRebuild();
}

整个过程可以分几步理解

  1. 这里创建了 StatelessElement, 创建成功后,frameWork 就会调用 mount方法,因为StatelessElement没有实现mount, 所以这里调用的是ComponentElementmount.
  2. mount 中调用了 _firstBuild方法进行第一次构建(注:这里调用的是实现类StatelessElement_firstBuild 方法)
  3. _firstBuild 方法最后调用的是super._firstBuild,也就是 ComponentElement_firstBuild 方法,最终走到了rebuild()。由于ComponentElement没有重写,所以最终调用的是Elementrebuild()方法。
  4. rebuild最终又会调用到ComponentElementPerformRebuild方法。如下:
 
  ('vm:notify-debugger-on-exception')
  void performRebuild() {
    if (!kReleaseMode && debugProfileBuildsEnabled)
      Timeline.startSync('${widget.runtimeType}',  arguments: timelineArgumentsIndicatingLandmarkEvent);
    assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
    Widget? built;
    try {
      assert(() {
        _debugDoingBuild = true;
        return true;
      }());
      // 调用 build ,这里调用的是实现类 StatelessElement 的,最终是调用到我们自己实现的 build 中
      built = build();
      debugWidgetBuilderValue(widget, built);
    } catch (e, stack) {
      // catch
    } finally {
      _dirty = false;
      assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
    }
    try {
      //最终调用 updateChild 方法
      _child = updateChild(_child, built, slot);
      assert(_child != null);
    } catch (e, stack) {
      //....
      _child = updateChild(null, built, slot);
    }
    if (!kReleaseMode && debugProfileBuildsEnabled)
      Timeline.finishSync();
  }
('vm:prefer-inline')
/// 这个方法下面户多次提到
Element inflateWidget(Widget newWidget, Object? newSlot) {
  assert(newWidget != null);
  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!;
    }
  }
  //创建对应的 element  
  final Element newChild = newWidget.createElement();
  assert(() {
    _debugCheckForCycles(newChild);
    return true;
  }());
  //  调用 mount 方法
  newChild.mount(this, newSlot);
  assert(newChild._lifecycleState == _ElementLifecycle.active);
  return newChild;
}

上面代码最终调用到了udpateChild 方法,这个方法在上面 Element 的生命周期里有说到。

updateChild 方法中就会判断 build 是否需要更新或者替换,如果需要替换,就会清除原来的,并且对新的 build 创建对应的的 element,并且在最后调用build 对应的 Elementmount 方法。这里的 Element 就不一定是 StateLessElement 了,而是看 build 方法中的 widget 对应的 Element是什么。

从上面流程来看,整个过程像一个环,最开始的framework调用mount 。在mount 中最终调用到了performRebuild,在performRebuild中通过我们实现的build方法,拿到对应的widget后,如果需要转换,就会重新创建widgetelement ,并且调用这个elementmount 方法。

大致流程如下:

请添加图片描述

StatefulElement

相比于StatelessWidget,StatefulWidget中多了一个State,在 StatefulWidget中通过creatState来创建一个State,如下:

abstract class StatefulWidget extend Widget {
  const StatefulWidget({key? key}) : super{key:key};
  
  
  StatefulElement creatElement() => StatefulElement(this);
  
  
  
  State createState();
}

通过上面代码可以看出 StatefulWidget 对应Element 正是 StatefulElement

class StatefulElement extends ComponentElement {
  StatefulElement(StatefulWidget widget)
    :_state = widget.creatState,
     super(widget){
       assert(state._element == null);
       state._element = this;
       state._widget = widget;
     }
    State<StatefulWidget> get state => _state!;
    State<StatefulWidget>? _state;
    
  
  Widget build() => state.build(this);
}

StatefulElement中通过调用 widget.createState获取到了state对象。StatefulElement 创建完成后,framework 就会调用StatelessElement中的mount方法了。

StatelessElement 不同的是:

  • StatelessElement 中是通过调用 widget.build(this) 方法
  • StatefulElement 中是通过调用 State.build(this) 方法
RenderObjectElement

MutiChildRenderObjectElement 是继承自 RenderObjectElement 的。使用情况如SizedBox,Flex.

class MultiChildRenderObjectElement extends RenderObjectElement {
    
      
  void mount(Element? parent, Object? newSlot) {
    //调用super.mount 将传入的 parent 插入到树中  
    super.mount(parent, newSlot);
   
    final List<Element> children = List<Element>.filled(widget.children.length, _NullElement.instance, growable: false);
    Element? previousChild;
     //遍历所有的child
    for (int i = 0; i < children.length; i += 1) {
      //加载child  
      final Element newChild = inflateWidget(widget.children[i], IndexedSlot<Element?>(i, previousChild));
      children[i] = newChild;
      previousChild = newChild;
    }
    _children = children;
  }
}  

abstract class RenderObjectElement extends Element {
  
  void mount(Element? parent, Object? newSlot) {
    //将传入的 parent 插入到树中  
    super.mount(parent, newSlot);
      
    //创建与element相关联的renderObject 对象
    _renderObject = widget.createRenderObject(this);
    //将element.renderObject 插入到渲染树中的指定位置  
    attachRenderObject(newSlot);
    _dirty = false;
  }
}    

大致步骤如下:

  • 首先是调用 MutiChildRenderObejectElementmount 方法中,先将传入的 parent 插入到树中
  • 接着 RenderObjectElement 中创建一个 renderObject 并添加到渲染树中的插槽的指定位置
  • 最后 MultiChildRenderObejectElement 中遍历所有的 child,并且调用 inflatWidget 创建 child 并插入指定插槽中。

总结:

通过上面的分析,知道了 Element 的生命周期以及他的调用过程。并且如果仔细观察那三个 Element 就会发现,上面的三种 Element 可以分为两类,分别是组合类绘制类

  • 组合类一般继承自 StatelessElement 或者 SatefulElement,他们属于组合类,并不参与绘制,内部嵌套了很多层 Widget,查看他们的 mount 方法,就会发现其中并没有创建 renderObject 并添加到渲染树。例如 StatelessWidgetStatefulWidgetText, ContainerImage 等。

  • 绘制类就是参加了绘制,在 mount 方法中,会有创建 renderObjectattachRenderObject 到渲染树中。如 Coloum, SizedBox 等。

    还有一种不常用的类,代理类 (inheritedWidget),对应的 ElementProxyElement

RenderObject

上面每提到一个 Element 都对应一个 RenderObject ,我们可以通过 Element.renderObject 来获取。并且 RenderObeject 的主要职责是Layout ,所有 RenderObject 会组成一个渲染树 RenderTree

通过上面的分析,可以看到树的核心就是 mount 方法。来看下 RenderObjectElement.mount 方法。


void mount(Element? parent, Object? newSolt) {
  super.mount(parent, newSlot);
  _renderObject = widget.creatRenderObject(this);
  attachRenderObject(newSlot);
  _dirty = false;
}

在执行完 super.mount 之后(将 parent 插入到 element 树中),执行了 attachRenderObject 方法。


void attachRenderObject(Object? newSlot) {
  assert(_ancestorRenderObjectElement == null);
  _slot = newSlot;
  //查询当前最近的 RenderObject 对象  
  _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
  // 将当前节点的 renderObject 对象插入到上面找的的 RenderObject下面  
  _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
  //.........	
}

RenderObjectElement? _findAncestorRenderObjectElement() {
   Element? ancestor = _parent;
   while (ancestor != null && ancestor is! RenderObjectElement)
     ancestor = ancestor._parent;
   return ancestor as RenderObjectElement?;
 }

上面代码是一个死循环,退出的时机就是 ancestor 父节点为空和父节点是 RenderObjectElement。说明这个方法会找到离当前节点最近的一个 RenderObjectElement 对象,然后调用 insertRenderObjectChild 方法,这个方法是一个抽象方法,可以看下里面的实现:

  • SingleChildRenderObjectElement

    void insertRenderObejectChild(RenderObeject child, Object? slot) {
      final RenderObejectWithChildMixin<RenderObject> renderObject = this.renderObject as RenderObjectWithChildMixin<RenderObject>;
      renderObject.child = child;
    }
    

    上面代码中,找到当前 RenderOjectElementrenderObject, 且将我们传入的 child 给了 renderObjectchild. 所以这样就将传入的 child 挂在了 RenderObject 树上。

  • MultiChildRenderObjectElement

    
    void insertRenderObjectChild(RenderObject child, IndexedSlot<Element?> slot) {
      final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject  = this.renderObject;
      assert(renderObeject.debugValidateChild(child));
      renderObejct.insert(child,after:slot.value?.renderObject);
      assert(renderObject == this.renderObject);
    }
    

    上面代码中,找到 renderObject 之后赋值给了 ContainerRenderObejctMixin<RenderObject>, 可以看下 ContainerParentDataMixin<RenderObject>在这个类

    // 具有子项列表的渲染对象的通用混合
    // 为具有双向链接的子项列表的渲染对象子类提供子模型
    
    mixin ContainerRenderObjectMixin<ChildType extends RenderObject, ParentDataType extends ContainerParentDataMixin<ChildType>> on RenderObject {}
    

    泛型 mixin 用于渲染一组子对象的对象。MultiChildRenderObjectElement 是有子列表的。

通过上面的注释可以看到一个词双向链表。所以 MultiChildRenderObjectElement 的子节点通过双向链表链接。上面的 insert 最终会调用到 _insertIntoChildList 方法,如下:

ChildType? _firstChild;
ChildType? _lastChild;
void _insertIntoChildList(ChildType child, { ChildType? after }) {
  final ParentDataType childParentData = child.parentData! as ParentDataType;
  _childCount += 1;
  assert(_childCount > 0);
  if (after == null) {
    // after 为 null,则插入到 _firstChild 中
    childParentData.nextSibling = _firstChild;
    if (_firstChild != null) {
      final ParentDataType _firstChildParentData = _firstChild!.parentData! as ParentDataType;
      _firstChildParentData.previousSibling = child;
    }
    _firstChild = child;
    _lastChild ??= child;
  } else {
    final ParentDataType afterParentData = after.parentData! as ParentDataType;
    if (afterParentData.nextSibling == null) {
      // insert at the end (_lastChild); we'll end up with two or more children 
      // 将 child 插入到末尾  
      assert(after == _lastChild);
      childParentData.previousSibling = after;
      afterParentData.nextSibling = child;
      _lastChild = child;
    } else {
      // insert in the middle; we'll end up with three or more children
      // 插入到中间
      childParentData.nextSibling = afterParentData.nextSibling;
      childParentData.previousSibling = after;
      // set up links from siblings to child
      final ParentDataType childPreviousSiblingParentData = childParentData.previousSibling!.parentData! as ParentDataType;
      final ParentDataType childNextSiblingParentData = childParentData.nextSibling!.parentData! as ParentDataType;
      childPreviousSiblingParentData.nextSibling = child;
      childNextSiblingParentData.previousSibling = child;
      assert(afterParentData.nextSibling == child);
    }
  }
}

根据上面的注释我们可以将其分为三部分,

  1. afternull 的时候将 child 插入到第一个节点,
  2. child 插入到末端,
  3. child 插入到中间。

以一个示例查看:

Column(
  child:[
    SizedBox(..),
    Text(data),
    Text(data),
    Text(data),
  ])

第一个 Stack 向上找到 Column(RenderObjectElement) 之后,调用这个方法,目前 afternull,则 _firstchild 就是 SizedBox (对应的 renderObejctRenderConstraineBox);

第二个 Text ,前面说过Text 是组合类型,所以它不会挂载到树中,通过查询源码看出最终的 text 用的是 RichTextRichText 向上查找到 Column(RenderObjectElement) 之后,调用这个方法,出入两个参数,第一个 childRichRext 对应的 RenderParagraph, 第二个 afterSizedBox 对应的 RenderConstrainedBox. 按照上面的逻辑,执行下面代码:

final ParentDataType afterParentData = after.parentData! as ParentDataType;
if (afterParentData.nextSibling == null) {
  // insert at the end (_lastChild); we'll end up with two or more children
  assert(after == _lastChild);
  childParentData.previousSibling = after;
  afterParentData.nextSibling = child;
  _lastChild = child;
} 

childchildParentData.previousSibling 指向第一个节点,将第一个接的 afterParentData.nextSibling 指向 child,最后让 _lastchild 执行 child

后面也是如此,当流程结束后就可以得到 RenderTree

  • 如果某一个位置的 Widget 和 新 Widget 不一致,才需要重新创建 Element

  • 如果某一位置 Widget 和新 Widget 一致时候(两个 widget 相等或者 runtimeTypekey 相等),则只需修改 RenderObject 的配置,不用进行耗时耗性能的 RenderObject 的实例化。

    • 因为 Widget 是非常轻量级的,实例化耗费性能很少,所以用它描述 APP 的状态(也就是configuration)的最好工具;

    • 重量级的 RenderObject(创建十分耗性能)则需要尽可能的少创建,并尽可能复用;

      在框架中, Element 是被抽离出来的,所以我们不需要经常和他们打交道,每个 Widget 的 build (BuildContext context)方法中传递的 context 就是实现了 BuildContext 接口的 Element。

更新时的三棵树

因为 Widget 是不可变的,当某个 Widget 的配置改变的时候,整个 Widget 树都要被重建。例如当我们改变一个 Container 的颜色的时候,框架就会触发重建整个 Widget 树的动作。因为有了 Element 的存在,Flutter 会对比新的 Widget 树中的第一个 Widget 和 之前的 Widget。接下来比较 Widget 树中第二个 Widget 和之前的 Widget,以此类推,直到 Widget 树比较完成,如上面的分析。

class ThreeTree extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Contariner(
      color: Colors.yellow,
      child: Container(
      	color: Colors.red
      )
    )
  }
}

Flutter 遵循一个基本原则,判断新的 Widget 和老的 Widget 是否是同一个类型:

  • 如果不是同一个类型,那就把 WidgetElementRenderObject分别从他们的树(包括他们的子树)上移除,然后重新创建新的对象;

  • 如果是同一个类型,那就仅仅修改 RenderObject中的配置,然后继续向下遍历;

  • 在示例中,ThreeTree Widget 是和原来一样的类型,他的配置也和原来的 ThreeTreeRender 一样的,所以什么都不会发生。下一个节点在 Wideget 树中是 Container Widget,它的类型和原来的一样的,但是他的颜色变化了,所以 RenderObject 的配置也会发生对应的变化,然后会重新渲染,其他的对象保持不变。

    这个过程非常快,因为 Widget 的不变性和轻量级使得他能够快速创建,这个过程中那些重量级的 RenderObject 则保持不变,直到与其对应类型的 Widget 从Widget树中被移除。

当Widget的类型发生改变时

class ThreeTree extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Contariner(
      color: Colors.yellow,
      child: ElevatedButton(
      	child: Text("three tree"),
      	onPressed: () {},
			);
    )
  }
}

和前面说的流程一样, Flutter 会从新的 Widget 树的顶端向下遍历,与原有树中的 Widget 类型对比。
请添加图片描述

因为 ElavateButton 的类型与 Element 树中对应位置的 Element 类型不同, Flutter 将会从各自的树上删除这个 Element 和相对应的 ContainerRender, 然后 Flutter 将会重建 ElevatedButton 相对应的 ``ElementRenderObject`。
请添加图片描述

当新的 RenderObject 树被重建后将会计算布局,然后绘制到屏幕上。Flutter 内部使用了很多优化方法和缓存策略来处理,所以我们不需要手动处理这些。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值