Flutter的渲染绘制概述

作为一个新兴的跨平台方案,Flutter在渲染方面做了什么工作?

关于Flutter Framework的架构图:

通过上图我们可以了解到Framework的底层是Flutter引擎,引擎主要负责图形绘制(Skia)、文字排版(libtxt)和提供Dart运行时,引擎全部使用C++实现,Framework层使我们可以用Dart语言调用引擎的强大能力。

1.Widget

Widget是 Flutter中控件实现的基本单位,其意义类似于 Cocoa Touch中的 UIView。Widget里面存储了一个视图的配置信息,包括布局、属性等待。所以它只是一份轻量的,可直接使用的数据结构。在构建为结构树,甚至重新创建和销毁结构树时都不存在明显的性能问题。但是我们开发一般是使用Meterial 或者 Cupertino的控件,所以当你往视图上添加了一个Widget后,背后支持这种渲染的是什么?

官方对Widget的注释分析:

/// Describes the configuration for an Element.(为Element提供配置信息)
复制代码

↑ 从这一句话,隐隐感觉到Widget和Element是紧密关联着的并且提供给Element某些信息。

/// If you wish to associate mutable state with a widget, consider using a
/// [StatefulWidget], which creates a [State] object (via
/// [StatefulWidget.createState]) whenever it is inflated into an element and
/// incorporated into the tree.

/* 
Widget会被填充到Element,并由Element管理底层渲染树。
Widget本身没有可变状态(所有的字段必须是final)。
如果想要把可变状态与Widget关联起来,可以使用StatefulWidget,
StatefulWidget通过使用StatefulWidget.createState方法创建State对象,
并将之扩充到Element以及合并到树中;	
*/
复制代码

↑ Widget并不会直接管理状态及渲染,而是通过State这个对象来管理状态。

/// 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.

/*
给定的Widget可以被包含在树中(零次或多次)。
一个给定的Widget可以放置在树中多次。
每次将一个Widget放入树中时,它都会被扩充到一个Element中,
这也意味着多次并入树中的Widget将会被多次扩充进对应的Element。
*/
复制代码

↑ 实际界面开发当中,一个视图树可能包含有多个TextWidget(widget可能被使用多次),但是这些都叫作TextWidget的widget却被填充进一个个独立的Element中。

/// The [key] property 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.

/*
Widget中的Key这个属性控制一个Widget如何替换树中的另一个Widget。
如果两个Widget的runtimeType和key属性相等(==),
则新的widget通过更新Element(即通过使用新的Widget调用Element.update)来替换旧的Widget。
否则,如果两个Widget的runtimeType和key属性不相等,则旧的Element将从树中被移除,
新的Widget将被扩充到一个新的Element中,这个新的Element将被插入树中。
*/
复制代码

↑Widget通过Key和runtimeType来决定自身在Element tree中,是更新,插入,还是删除等操作。

2.Element

开发者不用手动去操纵Element,多数情况是框架的内部逻辑在操作。

打开Element类,在里面发现了Element的两个重要属性widget和renderObject

  /// The configuration for this element.
  @override
  Widget get widget => _widget;
  Widget _widget;

  /// The render object at (or below) this location in the tree.
  ///
  /// If this object is a [RenderObjectElement], the render object is the one at
  /// this location in the tree. Otherwise, this getter will walk down the tree
  /// until it finds a [RenderObjectElement].
  RenderObject get renderObject {
    RenderObject result;
    void visit(Element element) {
      assert(result == null); // this verifies that there's only one child
      if (element is RenderObjectElement)
        result = element.renderObject;
      else
        element.visitChildren(visit);
    }
    visit(this);
    return result;
  }
复制代码

↑也就是说Element同时持有了Widgets和RenderObject。那Element的作用就比较明确了,Element作为中间件来分离控件树和渲染对象。

所以整个Element的生命周期的工作也明确了大概:

框架层通过调用即将被用来作为Element的初始化配置信息的Widget的Widget.
createElement方法来创建Element; 

框架层通过调用mount方法来将新创建的Element添加到给定父级中给定槽点的树上。
mount方法负责将任何Widget扩充到Widget并根据需要调用attachRenderObject,
以将任何关联的渲染对象附加到渲染树上。

此时,Element被视为“激活的”,并可能出现在屏幕上。

在某些情况下,父(Element)可能会更改用于配置此Element的Widget,
例如因为父Element重新创建了新状态。
发生这种情况时,框架层将调用新的Widget的update方法。
新Widget将始终具有与旧Widget相同的runtimeType和key属性。
如果父Element希望在树中的此位置更改Widget的runtimeType或key,
可以通过unmounting(卸载)此Element并在此位置扩充新Widget来实现。

在某些时候,祖先Element可能会决定从树中移除该Element(或中间祖先Element),
祖先Element自己通过调用deactivateChild来完成该操作。
停用中间祖先将从渲染树中移除该Element的渲染对象,
并将此Element添加到owner属性中的非活动元素列表中,
从而让框架层调用deactivate方法作用在此Element上。

此时,该Element被视为“无效状态”,
并且不会出现在屏幕上。一个Element可以保持非活动状态,
直到当前动画帧结束。在动画帧结束时,任何仍处于非活动状态的Element都将被卸载。

如果Element被重新组合到树中(例如,因为它或其祖先之一有一个全局键(global key)被重用),
框架层将从owner属性中的非活动Element列表中移除该Element,
并调用该Element的activate方法,
并重新附加Element的渲染对象到渲染树。 
(此时,Element再次被视为“活动状态”并可能出现在屏幕上。)

如果Element在当前动画帧的末尾没有被重新组合到树中,则框架层将调用该元素的unmount方法。

此时,该元素被视为“已停用”,并且将来不会并入树中。
复制代码

3.RenderObject

官方定义:An object in the render tree.渲染树中的一个对象。从其名字,我们可以很直观地知道,它就是负责渲染的工作,实际上,所有的布局、绘制和事件响应全都由它负责,开发复杂视图时我们可能经常需要与之打交道。

而它又是由 Element 的子类 RenderObjectElement 创建出来的,RenderObject 也会构成一个 Render Tree,并且每个 RenderObject 也都会被保存下来以便在更新时复用,Render Tree构建的数据会被加入到 Engine所需的 LayerTree中,Engine通过 LayerTree进行视图合成并光栅化,提交给 GPU。

1,2,3所构成的Tree很像iOS的模型树-->呈现树-->渲染树

4.Layout

当前面的准备工作完成后,具体展示在屏幕上时就需要布局。布局可以计算出每个节点所占空间的真实大小。在构建视图树时,节点的 size约束是从树顶部向底部的,而在计算布局的时候,遍历树是深度优先,所以获得布局数据的顺序是自下而上的。和 Web一样,Flutter的布局也是基于盒子模型。

5.Painting

在进行绘制时,每个节点都会先绘制自身,其次才是子节点。比如节点 2是一个背景色绿色的视图,在绘制完自身后,绘制子节点 3和 4,它们可能具有 alpha属性,所以绘制后当布局重叠时会合成红色的节点 5。所以从数据流方向看的时候,获得最终的 Layer的顺序反而是自下而上的。

Flutter使用边界标记需要重新布局和重新绘制的节点部分,在进入和走出重绘边界时,Flutter会强制切换新的图层,这样就可以避免其他节点被污染或者触发重建,这个边界被分别叫做 Relayout boundary 和 Repaint boundary。

总结:

所以每当我使用了一个Widget的时候,它在Flutter界面渲染过程经历了三个阶段:布局、绘制、合成,布局和绘制在Flutter框架中完成,合成则交由引擎负责。

参考资料:

Flutter view basic introduction Widget, Element, RenderObject

Flutter原理与实践

Flutter 渲染流水线浅析

Flutter 中文网

Flutter's Rendering Pipeline

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值