前篇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
三個方法。
來張最後效果圖吧。
評論和閱讀多的話,我儘快寫好下一篇文章。