Flutter中Widget与Element关系

Widget

Widget 是 Flutter 世界里对视图的一种结构化描述,你可以把它看作是前端中的“控件”或“组件”。Widget 是控件实现的基本逻辑单位,里面存储的是有关视图渲染的配置信息,包括布局、渲染属性、事件响应信息等。

实际上,Flutter 种真正代表屏幕上显示元素的类时Element,Widget 只是描述 Element 的配置数据,并且一个Widget 可以对应多个Element。

另外,由于 Widget 的不可变性,可以以较低成本进行渲染节点复用,因此在一个真实的渲染树中可能存在不同的 Widget 对应同一个渲染节点的情况,这无疑又降低了重建 UI 的成本。

//Widget 类继承自 DiagnosticableTree ,DiagnosticableTree为“诊断树”,主要作用是提供调试信息
abstract class Widget extends DiagnosticableTree {
  
  const Widget({ this.key });
//key,类似于 React/Vue 中的key,主要的作用是决定是否在下一次创建时复用旧的 Widget ,条件在canUpdate方法中
  final Key key;
//Flutter Framework在构建UI树时,会先调用此方法生成对应节点的Element对象。
  @protected
  Element createElement();

  @override
  String toStringShort() {
    final String type = objectRuntimeType(this, 'Widget');
    return key == null ? type : '$type-$key';
  }
//复写父类的方法,设置诊断树的一些特征
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }

  @override
  @nonVirtual
  bool operator ==(Object other) => super == other;

  @override
  @nonVirtual
  int get hashCode => super.hashCode;
//一个静态方法。主要用于Widget树重新创建时复用旧的Widget。只要runtimeType和key相同,就会用newWidget更新Element对象的配置
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }

  static int _debugConcreteSubtype(Widget widget) {
    return widget is StatefulWidget ? 1 :
           widget is StatelessWidget ? 2 :
           0;
    }
}

Element

Element 是 Widget 的一个实例化对象,它承载了视图构建的上下文数据,是连接结构化的配置信息到完成最终渲染的桥梁。

首先,通过 Widget 树生成对应的 Element 树;

然后,创建相应的 RenderObject 并关联到 Element.renderObject 属性上;

最后,构建成 RenderObject 树来完成布局的排列和绘制,以完成最终的渲染。

Element 生命周期

调用 Widget#createElement 创建一个Element实例,从而生成对应节点的 Element 对象。这里看下RenderObjectWidget 源码。对于 RenderObject 的创建与更新,其实是在 RenderObjectElement 类中完成的。

//RenderObjectWidget 是一个抽象类。这个类中同时拥有创建 Element、RenderObject,以及更新 RenderObject 的方法。
//实际上,RenderObjectWidget 本身并不负责这些对象的创建与更新。
abstract class RenderObjectWidget extends Widget {

  @override
  RenderObjectElement createElement();

  @protected
  RenderObject createRenderObject(BuildContext context);

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

  @protected
  void didUnmountRenderObject(covariant RenderObject renderObject) { }
}

调用 Element#mount ,mount方法首先调用 element 所对应的 Widget 的 createRenderObject 方法创建与 element 相关联的 RenderObject 对象,然后调用 Element#attachRenderObject 方法将 renderObject 添加到渲染树中插槽指定的位置。插入到渲染树后 element 就处于 active 状态。

abstract class Element extends DiagnosticableTree implements BuildContext {

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

  Widget get widget => _widget;
  Widget _widget;

  _ElementLifecycle _debugLifecycleState = _ElementLifecycle.initial;
  @mustCallSuper
  void mount(Element parent, dynamic newSlot) {
    if (parent != null) // Only assign ownership if the parent is non-null
      _owner = parent.owner;
    final Key key = widget.key;
    if (key is GlobalKey) {
      key._register(this);
    }
    _updateInheritance();
      _debugLifecycleState = _ElementLifecycle.active;
  }
}

abstract class RenderObjectElement extends Element {

//在 Element 创建完毕后,Flutter 会调用 Element 的 mount 方法。
//在这个方法里,会完成与之关联的 RenderObject 对象的创建,以及与渲染树的插入工作,
//插入到渲染树后的 Element 就可以显示到屏幕中了。
  @override
  void mount(Element parent, dynamic newSlot) {
    //...
    _renderObject = widget.createRenderObject(this);
    //...
    attachRenderObject(newSlot);  
  }
}

当父 Widget 的配置数据发生变化时,同事其 State#build 返回的Widget 结构与之前不同,此时就需要重新构建对应的 Element 树。当然,为使 Element 能够复用,在 Element 重新构建前后会首先尝试是否可以复用旧树上相同位置的 element,element 节点在更新之前都会调用与其对应的 Widget 的canUpdate 方法,如果返回 true,则复用,旧的 Element 会使用新的 Widget 配置数据更新,反之则会创建一个新额度 Element。

当有祖先 Element 决定要移除 element 时(如 Widget树结构发生了变化,导致与element 对应的 Widget被移除),这时 该祖先 Element 旧会调用 deactivateChild 方法来移除它,移除后 renderObject 也将从渲染树种移除,然后 Fragment 会 调用 deactivate 方法,这时状态变为 inactive。

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

这时 element 旧不会再显示到屏幕上了。为了避免再一次动画执行过程中反复创建、移除某个特定的element,inactive状态的element 在当前动画最后一帧结束前都会保留,如果在动画执行结束后它还未能重新变成 active 状态,那么 Flutter 将调用其 unmount 方法将其彻底移除,这时 element 的状态为 defunct,将永远不会被插入到树中。

  @mustCallSuper
  void unmount() {
    //...
    if (key is GlobalKey) {
      key._unregister(this);
    }
    assert(() {
      _debugLifecycleState = _ElementLifecycle.defunct;

  }

 

BuildContext

abstract class BuildContext {
}

Element 实现了这个接口,上文代码已有。在Element 的子类中,比如 以 StatelessWidget 为例

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

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

  @protected
  Widget build(BuildContext context);
}

 StatelessWidget 继承 Widget ,createElement 创建 StatelessElement ,并将 this(Widget) 传入 StatelessElement。StatelessElement就含有 StatelessWidget 的引用。

StatelessElement 中,调用父类构造函数,将 _widget 即 外界调用的 widget 赋值。这里得 widget 将其强转。

class StatelessElement extends ComponentElement {
  /// Creates an element that uses the given widget as its configuration.
  StatelessElement(StatelessWidget widget) : super(widget);

  @override
  StatelessWidget get widget => super.widget as StatelessWidget;

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

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

在 StatelessElement 得ubild中,将 this(Element) 作为参数调用 widget 的 build 方法,那么,我们常见的 build 中 BuildContext 就是 StatelessElement ,实则 BuildContext 就是 与Widget 对应的 Element,所以我们可以通过 context 在 StatelessWidget 的 build 方法中直接访问 Element 对象。

Flutter 尽量让开发者只需要关注 Widget 层即可,但是,有时并不能完全屏蔽,所以 Flutter FrameWork 在 build 方参数将 Element 对象传递出去。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值