Flutter渲染机制

190 阅读3分钟

Flutter树的渲染机制和原理

Flutter树的三大对象:WidgetElementRenderObject

Widget

Widget定义

在Flutter中,一切皆是组件。我们使用Flutter开发时就用了各种的Widget,但是实际渲染页面的并不是Widget,Flutter中对Widget的定义:

Describes the configuration for an [Element].

翻译过来就是 ”对一个[Element]配置的描述”

由此可见,Widget只是一个配置描述,并不是真正的渲染对象。那么什么是Element呢,简单分析一下Widget的源码:

@immutable
abstract class Widget extends DiagnosticableTree {
  /// Initializes [key] for subclasses.
  const Widget({ this.key });
  
  final Key? key;
  
  @protected
  @factory
  Element createElement();

Widget有一个构造,参数是Key,这里key的作用就是比较两个Widget是否是同一个Widget,如果两个Widget的runtimeType和key都相等,则复用Element进行更新,替换掉旧的Widget,否则,Element就会被从树中移除,新的Widget被扩充到新的Element中,然后再把这个新的Element挂载树中。

还有一个方法是createElement(),子类必须实现这个方法,大部分我们使用的系统的Widget比如StatefulWidget和StatelessWidget,都默认实现了这个方法,这个方法也非常简单,就是创建了一个Element。现在我们可以知道Widget与Element的关系:一个Widget有一个Element对象,通过createElement()创建。

Widget特性

Widget类被标注了@immutable,意味不可变的,想要改变Widget就得创建一个新的。

Element

Element定义

Flutter对Element的定义:

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

翻译之后就是 ”一个关联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)
    : assert(widget != null),
      _widget = widget;

  Element? _parent;
    
  @protected
  @pragma('vm:prefer-inline')
  Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
  			……
  }
    
  @mustCallSuper
  void mount(Element? parent, Object? newSlot) {
      		……
  }

mount方法

当一个Widget被创建时就会相应的创建关联的Element,framework就会调用mount方法来把这个Element插入到Element树中。

看一个最简单不过的Opacity类,这个Widget只有一个child和opacity属性,作用是控制子类的透明度。Opacity继承了SingleChildRenderObjectWidget,该类会创建一个Element:SingleChildRenderObjectElement

class SingleChildRenderObjectElement extends RenderObjectElement {
  /// Creates an element that uses the given widget as its configuration.
  SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);
    
  @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    _child = updateChild(_child, widget.child, null);
  }

这边调用了updateChild方法和父类的mount方法,看一下父类的mount,这边去除不必要的代码

abstract class RenderObjectElement extends Element {
  /// Creates an element that uses the given widget as its configuration.
  RenderObjectElement(RenderObjectWidget widget) : super(widget)
      
  /// The underlying [RenderObject] for this element.
  @override
  RenderObject get renderObject => _renderObject!;
  RenderObject? _renderObject;
      
  @override
  void mount(Element? parent, Object? newSlot) {
    super.mount(parent, newSlot);
    _renderObject = widget.createRenderObject(this);
    attachRenderObject(newSlot);
    _dirty = false;
  }

可以看到这里通过widget创建了一个RenderObject。

Element分别持有了Widget对象和RenderObject对象,到这我们可以明白一个Element包含了一个Widget和一个RenderObject,Element和RenderObject是由Widget生成的。

其中还有一个attachRenderObject方法:

@override
  void attachRenderObject(Object? newSlot) {
    assert(_ancestorRenderObjectElement == null);
    _slot = newSlot;
    _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
    _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
    final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();
    if (parentDataElement != null)
      _updateParentData(parentDataElement.widget);
  }

这个方法主要功能就是将RenderObject关联到RenderObject树上,这时可以认为Widget已经渲染在屏幕上了。

RenderObject

RenderObject定义

通过以上的说明,已经清楚Flutter中真正渲染页面的对象就是RenderObject

Flutter对RenderObject的定义:

An object in the render tree.

翻译过来就是 ”渲染树上的一个对象“

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
  /// Initializes internal fields for subclasses.
  RenderObject() {
    _needsCompositing = isRepaintBoundary || alwaysNeedsCompositing;
  }
    
  void layout(Constraints constraints, { bool parentUsesSize = false }) {
      ……
  }
    
  void paint(PaintingContext context, Offset offset) { }
    
  void performLayout();
  void markNeedsPaint() {
      ……
  }
    ……

这些方法可以看出来RenderObject的主要职责就是布局和绘制。