Flutter簡單實現手寫瀑布流 第二篇

Chinouo發表於2021-06-01

前篇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

相關文章