Widget、RenderObject 与 Element

我们在学习 Flutter 的时候,可能经常看到三个名词:Widget、RenderObject 和 Element ,弄懂这几个概念可能也是入门 Flutter 框架原理的第一步。

 

 

01

Widget 

 

在 Flutter 中,万物皆是 Widget,无论是可见的还是功能型的,那么 Widget 究竟是什么呢?

按照惯例,先看官方文档。

  • Widget 的作用是用来保存 Element 的配置信息的。

  • Widget 本身是不可变的。

  • Element 根据 Widget 里面保存的配置信息来管理渲染树。

  • Widget 可以多次的插入到 Widget 树中,每插入一次,Element 都要重新装载一遍 Widget 。

  • Widget 里面的 key 属性用来决定依赖这个 Widget 的 Element 在 Element 树中是更新还是移除。

接下来看一下 Widget 的定义。

 

abstract class Widget {
  const Widget({ this.key });
  final Key key;

  @protected
  Element createElement();

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

 

通过上面 Widget 的定义,可以看到有两个重要的方法,一个是通过 createElement 来创建 Element 对象的,一个是根据 key 来决定更新行为的 canUpdate 方法。

以 Opacity 为例,Opacity 做为一个 Widget ,只保存另一个配置信息:opacity,这个属性决定了透明度,范围在 0 到 1 之间。

Opacity 既然做为一个 Widget,肯定是 Widget 的子类,其继承关系如下:

 

  •  
Opacity → SingleChildRenderObjectWidget → RenderObjectWidget → Widget

 

Opacity 的定义如下:

 

class Opacity extends SingleChildRenderObjectWidget {

  const Opacity({
    Key key,
    @required this.opacity,
    this.alwaysIncludeSemantics = false,
    Widget child,
  }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
       assert(alwaysIncludeSemantics != null),
       super(key: key, child: child);

  final double opacity;

  @override
  RenderOpacity createRenderObject(BuildContext context) {
    return RenderOpacity(
      opacity: opacity,
      alwaysIncludeSemantics: alwaysIncludeSemantics,
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderOpacity renderObject) {
    renderObject
      ..opacity = opacity
      ..alwaysIncludeSemantics = alwaysIncludeSemantics;
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DoubleProperty('opacity', opacity));
    properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
  }
}

 

通过上面的代码,可以看到 opacity 是 final 类型的属性,只能做为构造函数参数传递进去,不可改变,因此如果要更新这个属性,必须新建一个 Opcity 对象,这也是为什么我们代码里的 Widget build(BuildContext context) 方法里面每次 build 都会创建新的对象实例的原因。

 

02

RenderObject

  • RenderObject 是做为渲染树中的对象存在的。

  • RenderObject 不会定义约束关系,也就是不会对子控件的布局位置、大小等进行管理。

  • RenderObject 中有一个 parentData 属性,这个属性用来保存其孩子节点的特定信息,如子节点位置,这个属性对其孩子是透明的。

 

RenderObject 的定义如下

 

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
  ParentData parentData;
  Constraints _constraints;
  void layout(Constraints constraints, { bool parentUsesSize = false }) {

  }
  void paint(PaintingContext context, Offset offset) { }

  void performLayout();
  void markNeedsPaint() {
  }
}

 

通过以上定义,可以看出,RenderObject 的主要作用就是绘制和布局的。

那么这个 RenderObject 是哪里来的呢?是在 Widget 里面创建的。如上面的 Opacity 中重写了 createRenderObject 方法来创建 RenderOpacity 对象。

 

  @override
  RenderOpacity createRenderObject(BuildContext context) {
    return RenderOpacity(
      opacity: opacity,
      alwaysIncludeSemantics: alwaysIncludeSemantics,
    );
  }

 

在 RenderOpacity 内部实现了布局、点击检测和大小计算等功能。

 

03

Element

  • Element 可以理解为是其关联的 Widget 的实例,并且关联在 Widget 树的特定位置上。

  • 由于 Widget 是不可变的,因此一个 Widget 可以同时用来配置多个子 Widget 树,而 Element 就用来代表特定位置的 Widget 。

  • Widget 是不可变的,但是 Element 是可变的。

  • 一些 Element 只能有一个子节点,如 Container,Opacity,Center 还有一些可以有多个子节点,如 Column ,Row 和 ListView 等。

 

Element 的 生命周期:

  • Flutter framework 通过 Widget.createElement 来创建一个 element 。

  • 每当 Widget 创建并插入到 Widget 树中时,framework 就会通过 mount 方法来把这个 widget 创建并关联的 element 插入到 element 树中(其父 element 会给出一个位置)。

  • 通过 attachRenderObject 方法来将 render objects 来关联到 render 树上,这时可以认为这个 widget 已经显示在屏幕上了。

  • 每当执行了 rebuid 方法,widget 代表的配置信息改变时(创建了一个新的 widget),framewrok 就会调用这个新的 widget 的 update 方法(新的 widget 的 和老的 widget 有相同的 runtimeType 和 key,如果不同,就要先 unmounting 然后重新装载 widget)。

  • 当 element 的祖先想要移除一个子 element 时,可以通过 deactivateChild 方法,先把这个 element 从 树中移除,然后将这个 element 加入到一个“不活跃元素列表”中,接着 framework 就会将这个 element 从屏幕移除(当下一个渲染帧到来这个 element 依然不活跃)。

由于 是在 Widget 中创建了Element,类似于 Widget 的继承关系,Element 的继承关系如下:

  •  
SingleChildRenderObjectElement → RenderObjectElement →  Element

 

接着看一下 Opacity 中,如何创建一个 Element 。Opacity 继承自 SingleChildRenderObjectElement,在 SingleChildRenderObject 中创建了 Element

 

@override
SingleChildRenderObjectElement createElement() => new SingleChildRenderObjectElement(this);

 

在 RenderObjectElement 中提供了 mount 方法

 

abstract class RenderObjectElement extends Element {
  RenderObjectElement(RenderObjectWidget widget) : super(widget);

  RenderObject _renderObject;

  @override
  void mount(Element parent, dynamic newSlot) {
    super.mount(parent, newSlot);
    _renderObject = widget.createRenderObject(this);
    attachRenderObject(newSlot);
    _dirty = false;
  }
}

 

通过上面的代码,我们能够发现,Element 中通过 widget.createRenderObject 方法也拿到了 RenderObject 对象,因此 Element 其实是同时包含 RenderObject 和 Widget 。

mount 方法会将 element 插入到 element 树中,mount 中还会调用 attachRenderObject 方法。

 

abstract class RenderObjectElement extends Element {
      @override
    void attachRenderObject(dynamic newSlot) {
      _slot = newSlot;
      _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
      _ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
      if (parentDataElement != null)
      _updateParentData(parentDataElement.widget);
    }
}
 

 

在这个方法里,通过 _findAncestorRenderObjectElement 方法, 找到了Element树上的祖先Element,如果祖先不为空,就调用insertChildRenderObject方法,这个方法的意思就是把renderObject的child替换成newSlot,然后通过 _updateParentData 用于更新布局数据的一些信息。

 

04

总结

 

上面只是简单介绍了一下 Flutter 中的 Widget 、RenderObject 和 Element 中的概念,而 Widget,Element和RenderObject体系是Flutter框架的核心 至于内部原理以及如果工作的,需要结合 Flutter 框架结构运行原理来看,这样才能更好的理解这些概念。

 

 

推荐阅读

 

深入理解 Flutter 多线程

Flutter 内部工作原理

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值