RenderObject如其名字一样,是用来渲染的对象,我们能在屏幕上看到画面,都是通过RenderObject“画”上去的。
通过之前的文章我们知道了Widget、Element、RenderObject之间的关系。
RenderObject在Flutter开发的中后期经常用到,如果想知道一个Widget的实现逻辑,只需要找到Widget的RenderObject实现即可,Flutter中自定义控件实现也绕不过RenderObject。
在Flutter中,最终页面的Layout、Paint等都发生在Widget对应的RenderObject子类中,而RenderObject也是Flutter跨平台最大的特点之一:所有的控件都和平台无关,Flutter只要求系统提供“Canvas”,开发者通过Widget生成的RenderObject“直接”通过引擎绘制到屏幕上
RenderObject分类
在Flutter中一般不会直接使用RenderObject,因为RenderObject本身只实现了基础的layout和paint等相关功能,而绘制到屏幕上还需要坐标体系和布局协议。所以大部分时候Widget的RenderObject会是其子类RenderBox
或者RenderSliver
从RenderObject层分类,Widget可以分为RenderBox
和RenderSliver
。Flutter中大部分控件对象都是继承RenderBox
实现的,可滑动区域的child使用RenderSliver
实现。
RenderBox
RenderBox继承自RenderObject,在其基础上实现了笛卡尔坐标系和布局协议。笛卡尔坐标系与Android和iOS等原生坐标系一致,都是以屏幕的left、top为原点,按照宽高分x、y轴,而布局协议一般由子类自定义决定。例如控件被绘制在x=10,y=20的位置,然后大小由parent对它进行约束显示。
对RenderBox抽象类一般会比较关注这几种方法:
1 |
|
setupParentData将ParentData转化为BoxParentData,在Flutter中ParentData主要存放父节点中所需要的子节点信息,每个RenderObject中都有ParentData变量,而且只能通过setupParentData赋值。
1 |
|
上面四个方法用来计算控件的最大、最小宽高,默认情况下都返回0,实际逻辑由RenderBox子类决定。
一般不会直接调用,而是用get***Intrinisc***
方法间接获取,因为getMinIntrinsicWidth
等内部方法内部会对计算结果进行缓存处理再返回。
1 |
|
用于计算基线,通过baseline的传递计算控件实现在y轴内的偏移,一般也不会被直接调用,而是通过getDistanceToBaseline
方法,因为这个方法会对计算结果进行缓存后返回。
1 |
|
执行布局逻辑,由子类自定义实现。
通常performLayout
一般会利用parent的Constraints计算自身的大小,并调用child的布局方法,调整child位置,最终输出一个Size取得宽高。所以在一般情况下RenderBox的performLayout流程是向下输入Constraints,然后向上返回Size。
1 | abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget { |
所有的RenderObject子类都必须实现paint
方法才会实现界面显示,并且该方法不是给用户直接调用的,需要更新绘制时,必须通过markNeedsPaint
方法触发界面执行paint绘制。markNeedsPaint
会通过requestVisualUpdate
方法触发引擎更新绘制界面,最终通过RendererBinding
的drawFrame
开始执行RenderObject的paint
方法。
在经历大小和布局位置计算之后,最终paint
方法会被调用。
PaintingContext: A place to paint,在父类ClipContext包含canvas对象用于绘制
Offset在paint方法中提供当前控件相对于屏幕的偏移值,提供绘制时确定的坐标。
RenderSliver
RenderSliver和RenderBox有很大的不同,RenderSliver输入的约束是SliverConstraints
,输出的大小是SliverGeometry
。这是因为RenderSliver
主要是在RenderViewport
中使用的。
1 | const SliverConstraints({ |
SliverConstraints
比BoxConstraints
参数复杂很多,BoxConstrains
只有最大、最小宽高,而SliverConstraints
有滑动方向、滑动偏移、滑动容器大小等相关参数
1 | const SliverGeometry({ |
相比于Size,SliverGeometry参数同样复杂很多,有滑动范围、绘制范围、偏移量等相关参数。
通过对比可以发现RenderSliver比RenderBox要复杂很多,RenderSliver更关注滑动、方向、缓存等关键点,这是因为sliver需要用于Viewport可滑动区域内的展示。一般在开发中使用的 ListView、GridView、CustomScrollView等都由sliver与 Viewport 组成,在可滑动区域内不允许直接使用RenderBox,只能使用RenderSliver嵌套后进行布局。