Flutter简单实现手写瀑布流 第二篇

1,895 阅读3分钟

前篇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的重写。

核心部分,也是最麻烦的部分

这个方法是对每张卡片对应的RenderBoxparentData进行参数设置,主要是设置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三个方法。

下篇文章在这里

来张最后效果图吧。

flow.gif