Flutter初识

诊断树简析

诊断树结构

        DiagnesticableTree,意为可诊断树。其内部持有一个链表结构,用于存储DiagnosticsNode结点对象。

abstract class DiagnosticableTree with Diagnosticable {
  ...

  /// Returns a list of [DiagnosticsNode] objects describing this node's
  /// children.
  ///
  /// Children that are offstage should be added with `style` set to
  /// [DiagnosticsTreeStyle.offstage] to indicate that they are offstage.
  ///
  /// The list must not contain any null entries. If there are explicit null
  /// children to report, consider [DiagnosticsNode.message] or
  /// [DiagnosticsProperty<Object>] as possible [DiagnosticsNode] objects to
  /// provide.
  ///
  /// Used by [toStringDeep], [toDiagnosticsNode] and [toStringShallow].
  ///
  /// See also:
  ///
  ///  * [RenderTable.debugDescribeChildren], which provides high quality custom
  ///    descriptions for its child nodes.
  @protected
  List<DiagnosticsNode> debugDescribeChildren() => const <DiagnosticsNode>[];
}

Widget

Widget即组件,是Flutter的实现页面元素的基础,其中最重要的是一个属性和一个方法:

  • 属性——key:用于计算此Widget是否需要在WidgetTree中进行替换【具体请看Diff算法

  • 方法——createElement():创建一个此Widget对应的Element

abstract class Widget extends DiagnosticableTree {
  /// Initializes [key] for subclasses.
  const Widget({ this.key });

  /// Controls how one widget replaces another widget in the tree.
  ///
  /// If the [runtimeType] and [key] properties of the two widgets are
  /// [operator==], respectively, then the new widget replaces the old widget by
  /// updating the underlying element (i.e., by calling [Element.update] with the
  /// new widget). Otherwise, the old element is removed from the tree, the new
  /// widget is inflated into an element, and the new element is inserted into the
  /// tree.
  ///
  /// In addition, using a [GlobalKey] as the widget's [key] allows the element
  /// to be moved around the tree (changing parent) without losing state. When a
  /// new widget is found (its key and type do not match a previous widget in
  /// the same location), but there was a widget with that same global key
  /// elsewhere in the tree in the previous frame, then that widget's element is
  /// moved to the new location.
  ///
  /// Generally, a widget that is the only child of another widget does not need
  /// an explicit key.
  ///
  /// See also:
  ///
  ///  * The discussions at [Key] and [GlobalKey].
  final Key? key;

  /// Inflates this configuration to a concrete instance.
  ///
  /// A given widget can be included in the tree zero or more times. In particular
  /// a given widget can be placed in the tree multiple times. Each time a widget
  /// is placed in the tree, it is inflated into an [Element], which means a
  /// widget that is incorporated into the tree multiple times will be inflated
  /// multiple times.
  @protected
  @factory
  Element createElement();
  ...
}

        Widget主要包含4个直接子类:

  • StatelessWidget:不需要可变状态的组件

  • StatefulWidget:具有可变状态的组件

  • ProxyWidget:内部拥有一个子组件

  • RenderObjectWidget:包裹了RenderObject(用于实际渲染)

Element

An instantiation of a [Widget] at a particular location in the tree.

——Flutter源码注释

        在这里我们先看下Element的构造方法,其主要将widget赋值给属性_widget,相当于Element持有了Widget(createElement方法是Widget中的抽象方法,相当于每个Widget都会生成属于自己的、与之对应的Element对象)

abstract class Element extends DiagnosticableTree implements BuildContext {
  /// Creates an element that uses the given widget as its configuration.
  ///
  /// Typically called by an override of [Widget.createElement].
  Element(Widget widget)
    : _widget = widget {
    if (kFlutterMemoryAllocationsEnabled) {
      MemoryAllocations.instance.dispatchObjectCreated(
        library: _flutterWidgetsLibrary,
        className: '$Element',
        object: this,
      );
    }
  }

  ...

  /// The configuration for this element.
  ///
  /// Avoid overriding this field on [Element] subtypes to provide a more
  /// specific widget type (i.e. [StatelessElement] and [StatelessWidget]).
  /// Instead, cast at any call sites where the more specific type is required.
  /// This avoids significant cast overhead on the getter which is accessed
  /// throughout the framework internals during the build phase - and for which
  /// the more specific type information is not used.
  @override
  Widget get widget => _widget!;
  Widget? _widget;

  ...
}

        Element包含两个直接子类:

  • ComponentElement:不是直接完成渲染的元素

  • RenderObjectElement:直接完成渲染的元素

RenderObject

An object in the render tree.

The [RenderObject] class hierarchy is the core of the rendering library's reason for being.

——Flutter源码注释

        RenderObject类是Flutter中,用于布局和绘制 UI 界面的对象,主要包含以下重要的属性和方法:

  • 重要属性:owner——存储渲染对象所属的渲染树的拥有者,管理着一个渲染树的一些重要任务,比如在每一帧中处理布局、绘制和组合

  • 重要方法:layout——布局、paint——绘制、showOnScreen——让RenderObject显示在屏幕上

abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarget {
  /// Initializes internal fields for subclasses.
  RenderObject() { ... }

  ...

  /// The owner for this render object (null if unattached).
  ///
  /// The entire render tree that this render object belongs to
  /// will have the same owner.
  PipelineOwner? get owner => _owner;
  PipelineOwner? _owner;

  /// Mark this render object as attached to the given owner.
  ///
  /// Typically called only from the [parent]'s [attach] method, and by the
  /// [owner] to mark the root of a tree as attached.
  ///
  /// Subclasses with children should override this method to
  /// [attach] all their children to the same [owner]
  /// after calling the inherited method, as in `super.attach(owner)`.
  @mustCallSuper
  void attach(PipelineOwner owner) {
    ...
    // owner赋值
    _owner = owner;
    ...
  }

  /// Mark this render object as detached from its [PipelineOwner].
  ///
  /// Typically called only from the [parent]'s [detach], and by the [owner] to
  /// mark the root of a tree as detached.
  ///
  /// Subclasses with children should override this method to
  /// [detach] all their children after calling the inherited method,
  /// as in `super.detach()`.
  @mustCallSuper
  void detach() {
    ...
    // owner重置
    _owner = null;
    ...
  }

  /// Compute the layout for this render object.
  ///
  /// This method is the main entry point for parents to ask their children to
  /// update their layout information. The parent passes a constraints object,
  /// which informs the child as to which layouts are permissible. The child is
  /// required to obey the given constraints.
  ///
  /// If the parent reads information computed during the child's layout, the
  /// parent must pass true for `parentUsesSize`. In that case, the parent will
  /// be marked as needing layout whenever the child is marked as needing layout
  /// because the parent's layout information depends on the child's layout
  /// information. If the parent uses the default value (false) for
  /// `parentUsesSize`, the child can change its layout information (subject to
  /// the given constraints) without informing the parent.
  ///
  /// Subclasses should not override [layout] directly. Instead, they should
  /// override [performResize] and/or [performLayout]. The [layout] method
  /// delegates the actual work to [performResize] and [performLayout].
  ///
  /// The parent's [performLayout] method should call the [layout] of all its
  /// children unconditionally. It is the [layout] method's responsibility (as
  /// implemented here) to return early if the child does not need to do any
  /// work to update its layout information.
  @pragma('vm:notify-debugger-on-exception')
  void layout(Constraints constraints, { bool parentUsesSize = false }) {
    ...
    if (sizedByParent) {
      try {
        // 仅使用约束更新渲染对象的大小
        performResize(); // 抽象方法,子类实现
        ...
      } catch (e, stack) {
        _reportException('performResize', e, stack);
      }
      ...
    }
    ...
    try {
      // 执行此渲染对象的布局计算工作
      performLayout(); // 抽象方法,子类实现
      markNeedsSemanticsUpdate();
      ...
    } catch (e, stack) {
      _reportException('performLayout', e, stack);
    }
    ...
  }

  ...
    try {
      paint(context, offset);
      ...
    } catch (e, stack) {
      _reportException('paint', e, stack);
    }
    ...
  }

  ...

  /// Paint this render object into the given context at the given offset.
  ///
  /// Subclasses should override this method to provide a visual appearance
  /// for themselves. The render object's local coordinate system is
  /// axis-aligned with the coordinate system of the context's canvas and the
  /// render object's local origin (i.e, x=0 and y=0) is placed at the given
  /// offset in the context's canvas.
  ///
  /// Do not call this function directly. If you wish to paint yourself, call
  /// [markNeedsPaint] instead to schedule a call to this function. If you wish
  /// to paint one of your children, call [PaintingContext.paintChild] on the
  /// given `context`.
  ///
  /// When painting one of your children (via a paint child function on the
  /// given context), the current canvas held by the context might change
  /// because draw operations before and after painting children might need to
  /// be recorded on separate compositing layers.
  void paint(PaintingContext context, Offset offset) { }

  ...

  /// Attempt to make (a portion of) this or a descendant [RenderObject] visible
  /// on screen.
  ///
  /// If `descendant` is provided, that [RenderObject] is made visible. If
  /// `descendant` is omitted, this [RenderObject] is made visible.
  ///
  /// The optional `rect` parameter describes which area of that [RenderObject]
  /// should be shown on screen. If `rect` is null, the entire
  /// [RenderObject] (as defined by its [paintBounds]) will be revealed. The
  /// `rect` parameter is interpreted relative to the coordinate system of
  /// `descendant` if that argument is provided and relative to this
  /// [RenderObject] otherwise.
  ///
  /// The `duration` parameter can be set to a non-zero value to bring the
  /// target object on screen in an animation defined by `curve`.
  ///
  /// See also:
  ///
  /// * [RenderViewportBase.showInViewport], which [RenderViewportBase] and
  ///   [SingleChildScrollView] delegate this method to.
  void showOnScreen({
    RenderObject? descendant,
    Rect? rect,
    Duration duration = Duration.zero,
    Curve curve = Curves.ease,
  }) {
    if (parent is RenderObject) {
      parent!.showOnScreen(
        descendant: descendant ?? this,
        rect: rect,
        duration: duration,
        curve: curve,
      );
    }
  }

  ...
}

Flutter中元素

Flutter中的三棵树

Flutter widgets are built using a modern framework that takes inspiration from React. The central idea is that you build your UI out of widgets. Widgets describe what their view should look like given their current configuration and state. When a widget’s state changes, the widget rebuilds its description, which the framework diffs against the previous description in order to determine the minimal changes needed in the underlying render tree to transition from one state to the next.

——Flutter官方文档

        Flutter借鉴于React,是一种类网页前端,在Flutter中也有类似虚拟DOM的Element。最早在HTML和早期JS时代,HTML会直接编译成为真实DOM树,只要其中的一个元素有所变化,整个页面就会刷新。这样,UI渲染就会占用大量CPU资源,导致页面性能下降,用户感受到卡顿等问题。因此,随后出现了虚拟DOM树隔离用户状态和真实DOM树,避免用户操作导致整个页面完全刷新,尽可能降低真实DOM树的刷新范围,提高页面性能。

        在Flutter中也存在类似的三棵树,即WidgetTree、ElementTree和RenderObjectTree。其中,Widget是开发者实际编写的树形结构;而Element是由Widget创建的,并分别持有Widget和RenderObject的引用;RenderObject则被Flutter引擎(Flutter Engine)调用并完成渲染操作。

详解Widget、Element、RenderObject关系

        之前我只是说这三者之间有关系,那么它们之间实际有关系吗?那就需要从源码中一探究竟。

Widget生成RenderObject

        在RenderObjectWidget中有createRenderObject()方法,用于创建RenderObject,它本身是一个抽象方法,由子类实现。

abstract class RenderObjectWidget extends Widget {
  ...
  @protected
  @factory
  RenderObject createRenderObject(BuildContext context);
  ...
}

        这样,属于RenderObjectWidget的组件就会生成一个RenderObject。同时我们可以看到,createRenderObject()方法声明在RenderObjectWidget类中,因此Widget和RenderObject并非是一一对应的关系,或者说并不是所有的Widget都会生成与之对应的RenderObject。

Widget生成Element

Element的创建

        在Widget中有createElement()方法,用于创建Element,本身也是抽象方法,由子类实现。

abstract class Widget extends DiagnosticableTree {
  ...
  @protected
  @factory
  Element createElement();
  ...
}

        我们通过源码可以看到,createElement()方法在Widget类中声明,因此Widget和Element是一一对应的关系,所有的Widget都会生成与之对应的Element。

三种Element
StatelessElement

        StatelessElement中最重要的就是执行了(widget as StatelessWidget).build(this)方法。这里的build方法其实就是我们在写自定义Widget时,重写的build方法,即Widget树。

从另一个方面来说,build方法传进this参数,也就是说在我们写自定义Widget中重写的Widget build(BuildContext context)中的BuildContext就是这个Widget生成的Element本身,Element类实现了BuildContext接口。

class StatelessElement extends ComponentElement {
  ...
  
  // 这里的build(this)就是我们在写自己的Widget时,重写的build方法
  @override
  Widget build() => (widget as StatelessWidget).build(this);

  ...
}
StatefulElement

        在StatefulElement中非常重要的一个属性就是_state,它是在StatefulElement的构造方法中,调用widget.createState()方法创建的。而widget.createState()方法就是我们在写自定义StatefulWidget中需要重写的State<MyState> createState()方法。

        至于为什么我们能在自定义State中调用widget,是因为state._widget = widget这段代码,把Widget传入了state属性中,这样我们就可以在自定义State中通过widget属性,直接使用自定义StatefulWidget中的各种属性以及方法。

class StatefulElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.
  StatefulElement(StatefulWidget widget)
      : _state = widget.createState(),...);
    ...
    state._element = this;
    state._widget = widget;
  }

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

  /// The [State] instance associated with this location in the tree.
  ///
  /// There is a one-to-one relationship between [State] objects and the
  /// [StatefulElement] objects that hold them. The [State] objects are created
  /// by [StatefulElement] in [mount].
  State<StatefulWidget> get state => _state!;
  State<StatefulWidget>? _state;
  ...
  }
RenderObjectElement

        RenderObjectElement会在mount方法中调用createRenderObject(this)方法,用来创建RenderObject对象。这样也可以让RenderObjectElement保持对RenderObect的引用。

abstract class RenderObjectElement extends Element {
  /// Creates an element that uses the given widget as its configuration.
  RenderObjectElement(RenderObjectWidget super.widget);

  /// The underlying [RenderObject] for this element.
  ///
  /// If this element has been [unmount]ed, this getter will throw.
  @override
  RenderObject get renderObject {
    assert(_renderObject != null, '$runtimeType unmounted');
    return _renderObject!;
  }
  RenderObject? _renderObject;

  @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    ...
    _renderObject = (widget as RenderObjectWidget).createRenderObject(this);
    ...
    super.performRebuild(); // clears the "dirty" flag
  }
}
Element小结

        简单来说,Element可以算是Flutter中三棵树中的管家,因为它同时持有了widget和render object的实例,甚至还保存了state状态信息。

mount()方法进行挂载

        在Element类中,还有一个非常重要的mount方法,我们单独拿出来进行解读。

RenderObjectElement

        RenderObjectElement的mount方法比较简单,主要是清除“dirty”标志位和render object更新元素树:

@override
void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot);
  ...
  attachRenderObject(newSlot);
  super.performRebuild(); // clears the "dirty" flag
}
ComponmentElement

        我们来看下ComponmentElement的mount()方法:

@override
void mount(Element? parent, Object? newSlot) {
  super.mount(parent, newSlot);
  ...
  _firstBuild();
}

        而_firstBuild()只是调用了rebuild()方法,进而调用了performRebuild()方法,因此我们直接来看ComponmentElement的performRebuild()方法。

/// Calls the [StatelessWidget.build] method of the [StatelessWidget] object
/// (for stateless widgets) or the [State.build] method of the [State] object
/// (for stateful widgets) and then updates the widget tree.
///
/// Called automatically during [mount] to generate the first build, and by
/// [rebuild] when the element needs updating.
@override
@pragma('vm:notify-debugger-on-exception')
void performRebuild() {
  Widget? built;
  try {
    ...
    built = build();
    ...
    debugWidgetBuilderValue(widget, built);
  } catch (e, stack) {
    ...
  } finally {
    // We delay marking the element as clean until after calling build() so
    // that attempts to markNeedsBuild() during build() will be ignored.
    super.performRebuild(); // clears the "dirty" flag
  }
  ...
}

        其中,最重要的就是built = build();这段代码,而这是一个抽象方法,由ComponmentElement的直接子类StatelessElement、StatefulElement等等实现。而build(this)方法实际上就是我们在自定义Widget中重写的build(BuildContext context)方法。

// StatelessElement
@override
Widget build() => (widget as StatelessWidget).build(this);

// StatefulElement
@override
Widget build() => state.build(this);

        结论:mount方法最终调用我们的build方法

相关参考

Flutter官方文档

Flutter的Widget-Element-RenderObject

Flutter渲染流程分析

跟我学flutter:细细品Widget(四)Widget 渲染过程 与 RenderObjectWidget

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值