Widget、RenderObject 与 Element

Widget、RenderObject 与 Element

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

1、 Widget

在 Flutter 中,万物皆是 Widget,不管是可见的仍是功能型的,那么 Widget 到底是什么呢?bash

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

  • 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 方法。ide

以 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 都会建立新的对象实例的缘由。ui

2、RenderObject

  • RenderObject 是作为渲染树中的对象存在的。
  • RenderObject 不会定义约束关系,也就是不会对子控件的布局位置、大小等进行管理。
  • RenderObject 中有一个 parentData 属性,这个属性用来保存其孩子节点的特定信息,如子节点位置,这个属性对其孩子是透明的。

RenderObject 的定义以下this

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 内部实现了布局、点击检测和大小计算等功能。

3、 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 用于更新布局数据的一些信息。

总结

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

参考:

Flutter的原理及美团的实践

Flutter, what are Widgets, RenderObjects and Elements

深刻了解Flutter界面开发


欢迎关注「Flutter 编程开发」微信公众号 。

相关文章
相关标签/搜索