Flutter RenderBox指南——绘制篇

通过RenderObjectWidget把RenderBox塞进界面

1、RenderBox

在flutter中,我们最常接触的,莫过于各种各样的widget了,但是,实际负责渲染的RenderObject是很少接触的(它们之间的关联可以看看闲鱼的这篇文章:https://www.yuque.com/xytech/flutter/tge705)。而作为一名天天向上的程序员,我们自然要去学习一下它的原理,做到知其然且知其所以然。本文会先来看看RenderBox的用法,以此抛砖引玉,便于后面继续深入flutter的绘制原理。

首先,RenderBox是RenderObject的子类,它将坐标系转换成我们熟知的笛卡尔坐标系,更便于使用。使用RenderBox进行绘制(本文暂时不讲child),我们需要做三件事:

(1)测量

第一步,我们需要确定视图大小,并赋值给父类的size属性。测量有两种情况,第一种是size由自身决定,第二种是由parent决定。

首先,由自身决定size的情况,需要在performLayout方法中完成测量,通过父类的constraints可得到满足约束的值:

@override void performLayout() { size = Size( constraints.constrainWidth(200), constraints.constrainHeight(200), ); }

第二种情况,size由parent决定,这种情况下视图大小应该完全通过parent提供的constraints测量,不存在其它因素。这种情况下,只要parent的约束不发生变化,就不会重新测量。

这种情况需要重写sizedByParent并返回true,然后在performResize中完成测量,并且performLayout中不能对size赋值! 感兴趣的同学也可以两边都写,看看会发生什么:)。

@override void performLayout() {} @override void performResize() { size = Size( constraints.constrainWidth(200), constraints.constrainHeight(200), ); } @override bool get sizedByParent => true; (2)绘制

RenderBox的绘制与android原生的view绘制非常相似,同样是Paint+Canvas的组合,而且api也非常接近,会非常容易上手。

@override void paint(PaintingContext context, Offset offset) { Paint paint = Paint() ..color = _color ..style = PaintingStyle.fill; context.canvas.drawRect( Rect.fromLTRB( 0, 0, size.width, size.height, ), paint); }

这样是不是就万事大吉了呢?如果通过上面的代码进行绘制,你会发现,不管在外层怎么设置位置,绘制出来的矩形都是固定在屏幕左上角的!怎么回事?

这里就是flutter中绘制与android的最大不同:在这里绘制的坐标系是全局坐标系,即原点在屏幕左上角,而非视图左上角。

细心的同学可能已经发现,paint方法中还有一个offset参数,这就是经过parent的约束后,当前视图的偏移量,绘制时应该将它考虑进去:

@override void paint(PaintingContext context, Offset offset) { Paint paint = Paint() ..color = _color ..style = PaintingStyle.fill; context.canvas.drawRect( Rect.fromLTRB( offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height, ), paint); } (3)更新

在flutter中,是由Widget的配置发生变更而引起的rebuild,而这就是我们要实现的第三步:当视图属性发生变更时,标记重新布局或重新绘制,当屏幕刷新时就会做相应的刷新。

这里涉及到两个方法:markNeedsLayout、markNeedsPaint。顾名思义,前者标记重布局,后者标记重绘。

我们需要做的,就是根据属性的影响范围,在更新属性时,调用合适的标记方法,例如color变化时调用markNeedsPaint,width变化时调用markNeedsLayout。另外,两者都需要更新的情况下,调用markNeedsLayout即可,不需要两个方法都调。

set width(double width) { if (width != _width) { _width = width; markNeedsLayout(); } } set color(Color color) { if (color != _color) { _color = color; markNeedsPaint(); } } 2、RenderObjectWidget (1)简介

上面讲了一大堆RenderBox的用法,但是,这玩意儿怎么用到我们熟知的Widget里面去?

按照正常流程,我们得实现一个Element和一个Widget,然后在Widget中创建Element,在Element中创建和更新RenderObject,另外还得管理一大堆状态,处理非常繁琐。所幸flutter为我们封装了这一套逻辑,即RenderObjectWidget。

相信对flutter有所了解的同学都对StatelessWidget和StatefulWidget不陌生,但其实,StatelessWidget和StatefulWidget仅负责属性、生命周期等的管理,在它们的build方法实现中都会创建RenderObjectWidget,通过它来实现与RenderObject的关联。

举个栗子,我们经常使用的Image是个StatefulWidget,对应的state的build方法中实际返回了一个RawImage对象,而这个RawImage是继承自LeafRenderObjectWidget的,这正是RenderObjectWidget的一个子类;再比如Text,它build方法中创建的RichText是继承自MultiChildRenderObjectWidget,这同样是RenderObjectWidget的一个子类。

我们再看看RenderObjectWidget顶部的注释即可明白:

RenderObjectWidgets provide the configuration for [RenderObjectElement]s, which wrap [RenderObject]s, which provide the actual rendering of the application.

大概意思就是RenderObject才是实际负责渲染应用的,而RenderObjectWidget提供包装了RenderObject的配置,方便我们使用。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpxfdp.html