Flutter中的Widgets、Elements和RenderObjects

Flutter 的核心思想是 Everything is Widget;但是什么是 Widget 它与我们常说的 Element 和 RenderObject 有什么关系呢,这里简单整理一下;

Widget

源码分析

abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });

  final Key key;

  @protected
  Element createElement();

  @override
  String toStringShort() {
    return key == null ? '$runtimeType' : '$runtimeType-$key';
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }

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

      简单分析源码和注释可得,Widget 继承自 DiagnosticableTree 是该树上的一个对象;

  1. Widget 描述了 Element 的配置,只是用来保存属性的容器;
  2. createElement() 用来生成一个 Element 对象并添加到 Widget 树中;
  3. toStringShort() 是对该 Widget 的简短说明,包括 Widget 类型和对应的 Key 等;
  4. canUpdate() 用来判断当前 Widget 是否重建,当两个新旧 runtimeType 和 key 相同时则更新 Widget 否则会新建一个 Widget 替代老旧的 Widget

子类 Widget

      Widget 主要有三类子类 Widget;分别是组合类 Widget(StatelessWidget/StatefulWidget)、代理类 Widget(ProxyWidget)、渲染类 Widget(RenderObjectWidget);

1. StatelessWidget / StatefulWidget

      StatelessWidget 是状态不可变的 Widget,主要通过 build() 方法,把一个或多个 Widget 整合成一个新的 Widget;这也完全符合 Flutter 【组合大于继承】的思想;StatelessWidget 的核心方法就是 build() 方法,把多个 Widget 组合包装成一个新的 Widget

abstract class StatelessWidget extends Widget {
  const StatelessWidget({ Key key }) : super(key: key);

  @override
  StatelessElement createElement() => StatelessElement(this);
  
  @protected
  Widget build(BuildContext context);
}

      StatefulWidget 是状态可变的 Widget,而其核心是 State 状态管理;常用的 setState(){} 便是用来更新重构 Widget

abstract class StatefulWidget extends Widget {
  const StatefulWidget({ Key key }) : super(key: key);

  @override
  StatefulElement createElement() => StatefulElement(this);

  @protected
  State createState();
}

      State 用于处理 StatefulWidget 业务逻辑和状态管理,有 _StateLifecycle 生命周期进行维护;

  1. created 对象创建,在 initState() 生命周期时执行;
  2. initialized 对象初始化,在 didChangeDependencies() 过程中执行;initState() 只是创建了 State 对象但是未初始化完成;
  3. ready 对象准备好且 dispose() 方法未执行;
  4. defunct 对象销毁,在 dispose() 执行时进行销毁;

2. ProxyWidget

      ProxyWidget 作为一个抽象的代理 Widget 并没有实质性的作用,只是在父类和子类需要传递信息时使用;主要有 InheritedWidget 和 ParentDataWidget 两类;

abstract class ProxyWidget extends Widget {
  const ProxyWidget({ Key key, @required this.child }) : super(key: key);

  final Widget child;
}

      使用过 Bloc 或 Provider 等状态管理的朋友都了解过 InheritedWidget,主要都是对 InheritedWidget 的优化和封装;可以在树结构中传递信息,当使用 InheritedWidget 时,子类状态变更时可以通知父类进行对应的变更;小菜简单理解为数据上移;

      而 ParentDataWidget 与 InheritedWidget 作用方向相反,用于为具有多个子类的 RenderObjectWidget 提供对于的配置等,例如 Stack 使用已定位好的父类 Widget 来定位每个子 Widget;小菜简单理解为数据下移;

      InheritedWidget 和 ParentDataWidget 涉及内容较多,小菜暂不做深入研究;

3. RenderObjectWidget

      RenderObjectWidget 是真正用于绘制渲染 Widget,一切在屏幕上展示的 Widget 根本上都离不开 RenderObjectWidget;它提供了 RenderObjectElement 的创建配置和 RenderObject 渲染对象的规定,提供了应用程序的实际渲染;

abstract class RenderObjectWidget extends Widget {
  const RenderObjectWidget({ Key key }) : super(key: key);

  @override
  RenderObjectElement createElement();

  @protected
  RenderObject createRenderObject(BuildContext context);

  @protected
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
  
  @protected
  void didUnmountRenderObject(covariant RenderObject renderObject) { }
}

      updateRenderObject 是在 Widget 更新后,修改对应的 RenderObject 对象,在每次更新时都会调用;didUnmountRenderObject 是在 RenderObject 在 Render Tree 中删除时调用;

Key / GlobalKey

      Key 可以用来控制在 Widget 重建时是否与其他 Widget 匹配,只有 Key 和 runtimeType 同时一致才会认为是同一个 WidgetKey 在构建相同类型 Widget 的多个实例时很有用,例如 List 列表中多个相同类型的 item,可以提高列表效率;

      GlobalKey 可以作为应用全局唯一标识,在整个 Widget 层级中都是唯一的,可以使用 GlobalKey 来检索与 Widget 关联的状态;

      Widget 与 Element 和 RenderObject 都是密不可分的,之后进一步学习 Element 和 RenderObject;小菜对底层的研究还不够深入;如有错误,请多多指导!

Elements

上面简单了解了一下 Widget 的相关知识,其中 Widget 是 immutable 不可变的,而 Widget 是如何做到更新重绘的,这就离不开 Element 和 RenderObject;小菜简单了解一下 Element 的相关小知识;

Element

      Element 是 Widget 在 UI 树具体位置的一个实例化对象;UI View 在 Element 层级结构中会构建一个真实的 Element Tree,是真正的视图树结构;Element 作为 Widget 和 RenderObject 之间的协调者,并根据 Widget 的变化来完成结点的增删改的操作;

源码分析

      Element 所涉及源码较长,小菜仅针对具体的方法和生命周期进行学习;

生命周期

enum _ElementLifecycle {
  initial,
  active,
  inactive,
  defunct,
}

      Element 的生命周期主要包括如下几分,分别是 initial 初始化,active 活跃状态,inactive 不活跃状态以及 defunct 失效状态;

针对方法

1. createElement

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

      创建一个使用指定 Widget 作为其配置的 Element;通过 Widget 调用 Widget.createElement 来创建 Element,作为 Element 的初始位置;

2. mount

@mustCallSuper
void mount(Element parent, dynamic newSlot) {
    ...
    _parent = parent;
    _slot = newSlot;
    _depth = _parent != null ? _parent.depth + 1 : 1;
    _active = true;
    if (parent != null) 
      _owner = parent.owner;
    if (widget.key is GlobalKey) {
      final GlobalKey key = widget.key;
      key._register(this);
    }
    _updateInheritance();
}

      mount() 会将新创建的 Element 添加到指定的父级 slot 插槽树中,通过调用 attachRenderObject 添加到渲染树上;

3. update

@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
    if (newWidget == null) {
      if (child != null) deactivateChild(child);
      return null;
    }
    if (child != null) {
      if (child.widget == newWidget) {
        if (child.slot != newSlot) updateSlotForChild(child, newSlot);
        return child;
      }
      if (Widget.canUpdate(child.widget, newWidget)) {
        if (child.slot != newSlot) updateSlotForChild(child, newSlot);
        child.update(newWidget);
        return child;
      }
      deactivateChild(child);
      assert(child._parent == null);
    }
    return inflateWidget(newWidget, newSlot);
}

      updateChild 是 Element 的核心方法,每当需要增加,修改,删除子 child 时都会调用;主要根据 Widget 的变化用于 Element 的更新,进而更新 UI 树;

newWidget == null newWidget != null
child == null Returns null. Returns new [Element].
child != null Old child is removed, returns null. Old child updated if possible, returns child or new [Element].
  1. 当更新后的 Widget 为 null 时,对应的子节点已经移除,如果当前 child 不为 null,则直接 remove 掉;
  2. 当更新后的 Widget 不为 null 且当前 child 为 null 时,说明新 Widget 是新创建的,则 inflateWidget 创建子节点;
  3. 当更新后的 Widget 不为 null 且当前 child 也不为 null 该节点存在时,若 child.widget == newWidget 说明子节点前后未发生变化,若 child.slot != newSlot 说明子节点在兄弟结点间移动了位置,此时 updateSlotForChild 更新节点位置;否则直接返回子节点;
  4. 当更新后的 Widget 不为 null 且当前 child 也不为 null 该节点存在时,若 Widget.canUpdate 为 true 说明可以用 newWidget 修改子节点,直接调用 update 更新即可;否则先将子节点移除再通过 newWidget 创建新的子节点;其中 canUpdate 主要是判断新旧 Widget 的 key 和 runtimeType 是否一致;

4. deactivate

@protected
void deactivateChild(Element child) {
    child._parent = null;
    child.detachRenderObject();
    owner._inactiveElements.add(child); // this eventually calls child.deactivate()
}

      deactivateChild 将指定 Element 已到非活动 Element 列表中,并将渲染对象从渲染树中移除;该方法可以阻止 Element 成为其子类;

5. activate

@mustCallSuper
void activate() {
    final bool hadDependencies = (_dependencies != null && _dependencies.isNotEmpty) || _hadUnsatisfiedDependencies;
    _active = true;
    _dependencies?.clear();
    _hadUnsatisfiedDependencies = false;
    _updateInheritance();
    
    if (_dirty)
      owner.scheduleBuildFor(this);
    if (hadDependencies)
      didChangeDependencies();
}

      activate() 为将 Element 重新合并到树上时,框架会从 inactive 非活跃 Element 列表中删除该元素,且该元素调用 activate 并将 Element 的渲染对象添加到渲染树上;

6. unmount

@mustCallSuper
void unmount() {
    if (widget.key is GlobalKey) {
        final GlobalKey key = widget.key;
        key._unregister(this);
    }
}

      unmount() 为当框架永远不会重新激活时调用;为了避免在一次动画执行过程中反复创建,移除特定 Element 时,非活跃状态的 Element 都会在当前动画过程最后一帧先保留,如果到动画结束后还未变成活跃状态,则调用 unmount() 将该 Element 彻底移除;

对应关系

  1. Widget.createElement 为 initial 从无到有的初始化生命周期;
  2. mount 为 initial 初始化状态到 active 活跃状态到生命周期过渡;
  3. update 只有在 active 活跃状态时才会调用;
  4. deactivate 为 active 活跃状态到 inactive 非活跃状态生命周期过渡;
  5. activate 为 inactive 非活跃状态到 active 活跃状态的生命周期过渡;
  6. unmount 为 inactive 非活动状态到 defunct 失效状态生命周期的过渡;

子类 Element

      Element 主要有组合类 ComponentElement 和渲染类 RenderObjectElement 两个子类;

ComponentElement

      ComponentElement 为组合类 Element,主要包括如下 StatelessElement / StatefulElement / ProxyElement 子类;其中各 Element 都是与 Widget 对应的;

StatelessElement

class StatelessElement extends ComponentElement {
  StatelessElement(StatelessWidget widget) : super(widget);

  @override
  StatelessWidget get widget => super.widget;

  @override
  Widget build() => widget.build(this);

  @override
  void update(StatelessWidget newWidget) {
    super.update(newWidget);
    assert(widget == newWidget);
    _dirty = true;
    rebuild();
  }
}

      StatelessElement 相对比较简单,主要是重写 update() 在需要发生变更时 rebuild() 即可;

StatefulElement

@override
void update(StatefulWidget newWidget) {
    super.update(newWidget);
    final StatefulWidget oldWidget = _state._widget;
    _dirty = true;
    _state._widget = widget;
    try {
      _debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
      final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;
    } finally {
      _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
    }
    rebuild();
}

      StatefulElement 是对应 StatefulWidget 的,包括完整的 Element 生命周期,其中在更新时会更新 State 和 rebuild()

ProxyElement

abstract class ProxyElement extends ComponentElement 
  ProxyElement(ProxyWidget widget) : super(widget);

  @override
  ProxyWidget get widget => super.widget;

  @override
  Widget build() => widget.child;

  @override
  void update(ProxyWidget newWidget) {
    final ProxyWidget oldWidget = widget;
    assert(widget != null);
    assert(widget != newWidget);
    super.update(newWidget);
    assert(widget == newWidget);
    updated(oldWidget);
    _dirty = true;
    rebuild();
  }

  @protected
  void updated(covariant ProxyWidget oldWidget) {
    notifyClients(oldWidget);
  }

  @protected
  void notifyClients(covariant ProxyWidget oldWidget);
}

      ProxyElement 作为一个抽象类,其子类是 ParentDataElement 和 InheritedElement;当 Widget 更新时调用 update()notifyClients() 用于新旧 Widget 确实已改变时调用;

RenderObjectElement

      RenderObjectElement 为渲染类型 Element 对应的是 RenderObjectWidgetRenderObjectElement 作为抽象类也继承了 Element 所有的生命周期方法;

      大多数的 RenderObjectElement 都只对应一个 RenderObject 即只有一个子节点,例如 RootRenderObjectElement / SingleChildRenderObjectElement;但也有特殊的,如 LeafRenderObjectElement子类没有子节点,以及 MultiChildRenderObjectElement 子类可以有多个子节;


      Element 作为 Widget 和 RenderObject 的协作者起到了承上启下的左右;下面简单学习下 RenderObject。

RenderObjects

        上面简单了解了一下 Widget 和 Element,其中 Widget 主要是存放渲染内容以及布局信息等,仅作为一个信息存储的容器;Element 主要用于存放上下文环境,遍历 UI View 视图树;而小菜今天尝试学习的 RenderObject 才是 UI View 真正的渲染部分;

RenderObject

      RenderObject 作为渲染树中的一个对象;其 layout() 和 paint() 是渲染库核心,负责管理布局和渲染等;RenderObject 定义了布局绘制协议,但并没定义具体布局绘制模型;

源码分析

      RenderObject 可以从多个维度研究,可以通过 layout() 和 paint() 对比 Android 的绘制流程,也可以根据其属性和交互的对象(parent / owner / child)来学习;小菜从头开始为了尽可能多的了解源码,尝试第二种方式进一步学习;

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
    AbstractNode _rootNode;
    ParentData parentData;
    Constraints _constraints;
    @protected
    Constraints get constraints => _constraints;
    PipelineOwner _owner;
    bool get attached => _owner != null;
    void setupParentData(covariant RenderObject child) {}
    void adoptChild(RenderObject child) {}
    void dropChild(RenderObject child) {}
    void attach(PipelineOwner owner) {}
    void detach() {}
}

parent 相关

1. ParentData

ParentData parentData;

void setupParentData(covariant RenderObject child) {
    assert(_debugCanPerformMutations);
    if (child.parentData is! ParentData)
      child.parentData = ParentData();
}

      RenderObject 包括两个重要属性 parent 和 ParentData 插槽;ParentData 做为一个预留的变量,由 parent赋值,传递信息给 child 的存储容器;通常所有和 child 特定的数据都可以存储在 ParentData 中;

2. Constraints

void layout(Constraints constraints, { bool parentUsesSize = false }) {
    RenderObject relayoutBoundary;
    if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
      relayoutBoundary = this;
    } else {
      final RenderObject parent = this.parent;
      relayoutBoundary = parent._relayoutBoundary;
    }
   
    if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) return;
    _constraints = constraints;
    if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
      visitChildren((RenderObject child) {
        child._cleanRelayoutBoundary();
      });
    }
    _relayoutBoundary = relayoutBoundary;
    
    if (sizedByParent) {
      try {
        performResize();
      } catch (e, stack) {
        _debugReportException('performResize', e, stack);
      }
    }
    RenderObject debugPreviousActiveLayout;
    try {
      performLayout();
      markNeedsSemanticsUpdate();
    } catch (e, stack) {
      _debugReportException('performLayout', e, stack);
    }
    _needsLayout = false;
    markNeedsPaint();
}

      Constraints 作为 RenderObject 中 parent 和 child 之间的布局约束;layout() 作为 RenderObject 的核心方法,需要传入 Constraints 作为约束,配合 parentUsesSize 判断 RenderObject 在 child 子节点发生变化时,parent 父节点是否需要重新绘制;

3. relayoutBoundary

RenderObject relayoutBoundary;
if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
  relayoutBoundary = this;
} else {
  final RenderObject parent = this.parent;
  relayoutBoundary = parent._relayoutBoundary;
}
if (_relayoutBoundary != null && relayoutBoundary != _relayoutBoundary) {
  visitChildren((RenderObject child) {
    child._cleanRelayoutBoundary();
  });
}

void markNeedsLayout() {
    if (_needsLayout) {
      return;
    }
    if (_relayoutBoundary != this) {
      markParentNeedsLayout();
    } else {
      _needsLayout = true;
      if (owner != null) {
        owner._nodesNeedingLayout.add(this);
        owner.requestVisualUpdate();
      }
    }
}

      layout() 中定义了一个 RenderObject 类型的 relayoutBoundary 布局边界,如果布局边界发生变化,则遍历清空所有已记录的边界并重新设置;

      markNeedsLayout() 中也需要进行布局边界判断,若 RenderObject 自身不是 relayoutBoundary,则向 parent 父节点查找,直到找到确定是 relayoutBoundary 的 RenderObject 并标记为 dirty

      layout() 确定自己是否为边界需要判断四个条件,分别是 !parentUsesSize parent 父节点是否关心自己的大小;sizedByParent 是否由 parent 父节点判断大小;constraints.isTight 是否严格约束;parent is! RenderObject自身是否为 root 根节点;

owner 相关

      PipelineOwner 作为整个渲染流程的管理者;提供用于驱动渲染管道的接口,并存储在管道的每个阶段中已请求访问渲染对象的状态等;

1. flushLayout

void flushLayout() {
    if (!kReleaseMode) {
      Timeline.startSync('Layout', arguments: timelineWhitelistArguments);
    }
    try {
      while (_nodesNeedingLayout.isNotEmpty) {
        final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
        _nodesNeedingLayout = <RenderObject>[];
        for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
          if (node._needsLayout && node.owner == this)
            node._layoutWithoutResize();
        }
      }
    } finally {
      if (!kReleaseMode)  Timeline.finishSync();
    }
}

      flushLayout() 用于遍历所有标记为 dirty 的需要重新布局的 RenderObjects 并重新计算其布局尺寸和位置等;

2. flushCompositingBits

void flushCompositingBits() {
    if (!kReleaseMode) {
      Timeline.startSync('Compositing bits');
    }
    _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
    for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
      if (node._needsCompositingBitsUpdate && node.owner == this)
        node._updateCompositingBits();
    }
    _nodesNeedingCompositingBitsUpdate.clear();
    if (!kReleaseMode) {
      Timeline.finishSync();
    }
}

      flushCompositingBits() 用于遍历所有标记为 dirty 的需要 CompositingBitsUpdate 合并更新的子节点,再次阶段,每个 RenderObject 都会了解其子节点是否需要合并更新;

3. flushPaint

void flushPaint() {
    if (!kReleaseMode) {
      Timeline.startSync('Paint', arguments: timelineWhitelistArguments);
    }
    try {
      final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
      _nodesNeedingPaint = <RenderObject>[];
      for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
        if (node._needsPaint && node.owner == this) {
          if (node._layer.attached) {
            PaintingContext.repaintCompositedChild(node);
          } else { node._skippedPaintingOnLayer(); }
        }
      }
    } finally {
      if (!kReleaseMode) { Timeline.finishSync(); }
    }
}

      flushPaint() 用于遍历所有标记为 dirty 的需要重新绘制的子节点,并生成 Layer 用于绘制展示;

4. attach / detach

@override
void attach(PipelineOwner owner) {
    super.attach(owner);
    if (_needsLayout && _relayoutBoundary != null) {
      _needsLayout = false;
      markNeedsLayout();
    }
    if (_needsCompositingBitsUpdate) {
      _needsCompositingBitsUpdate = false;
      markNeedsCompositingBitsUpdate();
    }
    if (_needsPaint && _layer != null) {
      _needsPaint = false;
      markNeedsPaint();
    }
    if (_needsSemanticsUpdate && _semanticsConfiguration.isSemanticBoundary) {
      _needsSemanticsUpdate = false;
      markNeedsSemanticsUpdate();
    }
}

      layout() 中在 attach() 和 detach() 中也需要 PipelineOwnerattach() 主要通知管理者 owner 将其插入到渲染树中标记需要计算布局 layout 并调用 markNeedsPaint 重新绘制;detach() 主要是通知管理者取消关联;

child 相关

      对于 child 子节点,小菜主要学习如下三个方法;

1. adoptChild

@override
void adoptChild(RenderObject child) {
    setupParentData(child);
    markNeedsLayout();
    markNeedsCompositingBitsUpdate();
    markNeedsSemanticsUpdate();
    super.adoptChild(child);
}

      adoptChild() 主要是 RenderObject 添加一个 child 子节点;其中需要通过 setupParentData() 来获取 ParentData 中的数据并更新;

2. dropChild

@override
void dropChild(RenderObject child) {
    child._cleanRelayoutBoundary();
    child.parentData.detach();
    child.parentData = null;
    super.dropChild(child);
    markNeedsLayout();
    markNeedsCompositingBitsUpdate();
    markNeedsSemanticsUpdate();
}

      dropChild() 是和 adoptChild() 对应的方法,主要用于 RenderObject 删除一个 child 子节点;删除过程中需要 _cleanRelayoutBoundary 清除边界并删除 ParentData,之后再更新;

3. paintChild

void paintChild(RenderObject child, Offset offset) {
    if (child.isRepaintBoundary) {
      stopRecordingIfNeeded();
      _compositeChild(child, offset);
    } else {
      child._paintWithContext(this, offset);
    }
}

      paintChild() 为绘制一个子节点的 RenderObject;如果该子节点有自己合成层,则 child 子节点将被合成到与此绘制相关的上下文相关的 Layer 层中;

RenderBox

      RenderObject 并没定义具体布局绘制模型,所以小菜简单学习了一下 RenderBoxRenderBox 是 RenderObject 的子类,以屏幕左上角为原点(包括顶部状态栏)坐标系;BoxParentData 作为 child 子节点传输数据,BoxConstraints 作为其约束条件,通过 Size 记录其尺寸大小;可以定义具体的布局绘制模型。

猜你喜欢

转载自blog.csdn.net/jdsjlzx/article/details/123699753
今日推荐