Flutter渲染机制

Flutter渲染机制

学flutter有大半年了,现在写点flutter相关的知识,一是为了巩固自己,二是希望对大家深入理解flutter有所帮助,本篇文章参考陈航写的Flutter核心技术与实战。

flutter界面渲染过程

app界面的每一个widget都是以树的形式组织的,Flutter通过树中的每个widget创建不同类型的渲染对象,组成渲染对象树,其展示过程分为布局、绘制、合成和渲染四个阶段

布局

Flutter采用深度优先机制遍历渲染对象树,决定各渲染对象在屏幕上的位置和尺寸。

绘制

布局过程后,渲染对象树中的每个节点都有了明确的位置和尺寸,Flutter会把所有渲染对象绘制在不同图层,与布局过程一样,绘制过程也是深度优化遍历,即先绘制本身,再绘制子节点。

合并

合并是为了简化渲染树,提高渲染效率。因为一个app业务多了,页面也会很复杂,也会有很多重复渲染的图层,所以要将所有图层根据大小、层级、透明度等规则计算出最终需要显示的效果。

渲染

合并完成后,Flutter会将最终的图层数据交给Skia引擎加工成二维图像数据,最后交给GPU进行渲染,完成界面的展示

Widget的渲染过程

前面说到整个界面的渲染过程,可能比较抽象,这里通过界面中widget的渲染,从源代码分析Flutter的渲染过程。

大家应该知道视图树可以结构化的组织视图数据,提供给渲染引擎。而Flutter把视图数据的组织和渲染抽象为三部分,即Widget、Element和RenderObject。

Widget

Flutter将Widget设计成不可变的,这意味着当widget中视图渲染的配置信息发生改变时,Flutter会用重建Widget树的方式进行数据更新,而不是用命令修改来更新数据。这样就会有个问题,就是会涉及大量对象的销毁和创建,对垃圾回收造成压力。

Element

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

这里从源码看,Flutter渲染过程可以分为三步:
1.通过Widget树生成对应的Element树。
2.创建相应的RenderObject并关联到Element.renderObject属性上。
3.构建成RenderObject树,完成最终的渲染。

根据渲染过程可以看到Element同时持有Widget和RenderObject,所以说它是桥梁。并且中间层Element的加入,极大的减少了渲染带来的的性能损耗。

为什么这么说呢?
因为Widget是不可变的,但是Element是可变的。Element树这一层将Widget树的变化做了抽象,可以只将真正需要修改的部分同步到真实的RendenrObject树中,最大程度降低对真实渲染视图的修改,提高渲染效率,而不是销毁整个渲染视图树进行重建。

RenderObject

RenderObject是主要负责实现视图渲染的对象。
前面说到的渲染对象树在Flutter的展示过程分为布局、绘制、合并和渲染四个阶段。其中的布局和绘制就是在RenderObject中完成。

RenderObjectWidget

abstract class RenderObjectWidget extends Widget {
  @override
  RenderObjectElement createElement();
  @protected
  RenderObject createRenderObject(BuildContext context);
  @protected
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
  ...
}

RenderObjectWidget是一个抽象类,通过RenderObjectWidget的源码可以看到,这个类同时拥有创建Element、RenderObject以及更新RenderObject的方法。

实际上,RenderObjectWidget本身并不负责这些。

对于Element的创建,Flutter会在遍历Widget树时,调用createElement去同步Widget自身配置信息,从而生成对应节点的Element对象。

对于RenderObject的创建与更新,其实是在RenderObjectElement类中完成的。

abstract class RenderObjectElement extends Element {
  RenderObject _renderObject;

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

在Element创建完毕后,Flutter会调用Element的mount方法,在这个方法里会完成与之关联的RenderObject对象的创建,以及与渲染树的插入。

如果Widget的配置数据发生了改变,那么持有该Widget的Element节点也会被标记为dirty。在下一个周期的绘制时,Flutter就会触发Element树的更新,并使用最新的Widget数据更新自身以及关联的RenderObject对象。然后便会进入到Layout和Paint流程。

abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
  ...
  void layout(Constraints constraints, { bool parentUsesSize = false }) {...}
  
  void paint(PaintingContext context, Offset offset) { }
}

布局和绘制完成后,接下来就交给Skia引擎了。

总结

介绍了Flutter界面渲染过程和Widget的渲染过程,可以看出Widget和Element是一一对应的,RenderObject不是,只有实际需要布局和绘制的控件才会有RenderObject

最后希望这篇文章对你学习Flutter有所帮助。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值