前篇Flutter简单实现手写瀑布流 Widget部分请看这。
封面为加载好图片后,快速滑动的帧数图。
RenderObject的实现
前篇的Widget中有个createRenderObject的方法,这个方法即创建布局的信息并绘制使用。通过查阅RenderSliverList的源码,发现其是通过继承RenderSliverMultiBoxAdaptor类实现的布局,并重写了performLayout方法。该方法即为设置每个元素该放置的位置。
通过查阅源码,我们得知该类是抽象类,有3个mixin。
重要成员以及方法如下
//本质是个管理布局中大量卡片的Element
final RenderSliverBoxChildManager _childManager;
//设置每张卡片的布局信息,比如相对于滑动起点的偏移量
void setupParentData(RenderObject child);
//初始化布局使用,稍后会谈及
bool addInitialChild({ int index = 0, double layoutOffset = 0.0 }) ;
//给定RenderBox的下标,即是第几张卡片
int indexOf(RenderBox child);
//返回卡片的绘制高度
double paintExtentOf(RenderBox child) ;
//返回给定卡片相对于滑动起点的偏移量
double? childScrollOffset(RenderObject child);
//调用,绘制输出到屏幕上
void paint(PaintingContext context, Offset offset);
复制代码
坑点提示(可以先跳过)
1.RenderBox的layout方法的形参中,parentUsesSize属性一定要设置成true,不然父级元素无法访问其大小信息。
2.使用childManager.creatChild创建新的卡片或者使用移除旧的卡片childManager.removeChild方法的时候,记得将其写在 invokeLayoutCallback((SliverConstraints constraints){}) 里边,来告知RenderObject要改变RenderTree,否则会出现红色报错页面,该调用在RenderObject的源码中有写到原因。
实现过程
gridDelegate的实现
首先,我们必须要有一个用于设置副轴上的应当放置几张卡片的类,这里我模仿了SliverGrid对应的SliverGridDelegateWithFixedCrossAxisCount实现。
class FlowSliverDelegateWithFixedCrossAxisCount extends FlowSliverDelegate {
const FlowSliverDelegateWithFixedCrossAxisCount({
required this.crossItemCount,
});
final int crossItemCount; //设置副轴能放几张卡片
}
复制代码
ParentData的实现
接着,我们的每张卡片应该有一个相对于主轴的偏移量,即记录在第几列的信息,供布局使用,这里继承了SliverMultiBoxAdaptorParentData,父类提供了index,keepAlive变量信息。ParentData是RenderBox的一个属性,当坑点1中的属性为true时,父级才可以访问其信息!
class FlowSliverParentData extends SliverMultiBoxAdaptorParentData {
//主轴偏移量 可以认为是距离手机屏幕左边几个像素
double crossAxisPosition = 0.0;
}
复制代码
paint方法的重写
paint是运行时最后被调用的,但我把它放在此处是因为内容不多。下面给出该方法里面的几个重要常量,其他的照葫芦画瓢即可。注意,正真的绘制部分,是以屏幕为坐标系,左上角为零点。下面参数是描述绘制对象在该坐标轴的信息!
//这里的child是RenderBox
//应该绘制在离屏幕顶部多少像素
final double mainAxisDelta = childMainAxisPosition(child);
//应该绘制在离屏幕左边多少像素
final double crossAxisDelta = (child.parentData as FlowSliverParentData).crossAxisPosition;
//好的,我开始画了
context.paintChild(child, childOffset);
复制代码
performLayout的重写。
核心部分,也是最麻烦的部分
这个方法是对每张卡片对应的RenderBox
的parentData
进行参数设置,主要是设置parentData中layoutOffset(相对滑动起点的偏移量)
,crossAxisPosition(相对屏幕左边的偏移量)
,使其在待会paint()
方法有正确的位置信息。
在这里,我们必须要做三件事。
1.使用父元素(viewPort)给的constraints信息进行布局设置。(不知道是啥的去翻翻第一篇文章的基础知识链接)
2.对出现在视窗范围内的RenderBox进行layout()
调用,并且设置其parentData,使其有正确的位置信息。
3.输出布局信息给geometry变量。(不知道是啥的同上,这里是SliverGeometry
)
布局算法会在下篇文章讲出,同时给出源码(太差劲了,得改改,暂时没写)。
用于performLayout的方法
1.创建和销毁元素使用childManager的提供的remove和create方法,注意坑点。
2.参照SliverList中使用childManager带有的输出geometry信息的estimateMaxScrollOffset
,calculatePaintOffset
,calculateCacheOffset
三个方法。
来张最后效果图吧。
评论和阅读多的话,我尽快写好下一篇文章。