flutter 如何自定義一個loadmore / 載入更多

CaiJingLong發表於2018-09-24

寫在前面

這類的庫在pub上有很多

我為什麼要自定義呢

首先是專案需要,並且這種庫普適性高,抽取出來今後複用也方便點

另外記錄一下編碼思路,方便後續檢視

pub地址 pub國內映象
github

使用說明

匯入說明看這裡中文映象

image.png

看看構造方法
一共5個屬性
child是ListView

onLoadMore是載入更多時的回撥,由外部實現

isFinish 載入完成

delegate是一個抽象類

image.png

有預設實現, 其中有3個方法,一個是根據狀態給一個widget高度
一個是延遲載入的毫秒時間
一個是構建顯示在內部的Widget,這樣就完全實現了外部可根據狀態自定義Widget

LoadMoreTextBuilder 是一個根據狀態構建文字的方案,預設實現了 中文/英文文字,如果只想修改文字,使用預設樣式的話,可以直接用這個即可

思路

首先考慮怎麼自定義
一般來講有2種方式,一個是到底部繼續上拉載入,另一種是滾動到底部自動載入,我這裡採取的是到底部自動載入方案

不使用上拉載入的原因是:滾動到底繼續上拉不符合正常人習慣,如果是慣性滾動到底,誰知道你後面還有沒有東西的

思考如何自定義

首先怎麼樣可以知道滾動到底了呢,最簡單的方式,listview的最後一行build的時候一定滾動到底了

所以我們可以使用如下的方式定義

  
class _ListViewDemoPageState extends State<ListViewDemoPage> {  
var count = 10;  
  
@override  
Widget build(BuildContext context) {  
return ListView.builder(  
itemCount: count + 1,  
itemBuilder: _buildItem,  
);  
}  
  
Widget _buildItem(BuildContext context, int index) {  
if (index == count) {  
return Text('到底了');  
}  
return Text(index.toString());  
}  
}  
  
複製程式碼

這樣的話 只要到最後了,你自然知道應該載入了

可是這個方法不優雅啊,我們應該封裝為Widget控制元件,方便複用,接下來就要開始分析怎麼自定義了

首先觀察ListView的構造方法
有如下幾個構造方法

ListView:同名構造方法
ListView.builder
ListView.separated
ListView.custom

這裡custom,需要自定義childrenDelegate 型別為SliverChildDelegate,我先暫時不考慮

看其他的三個,發現內部都實現了這個Delegate

image.png

image.png

image.png

那麼我們接收一個ListView,然後判斷其中的delegate型別,然後分別進行處理不就可以了嗎

image.png

image.png

這裡我們分別處理,一個是增加一個count,另一個因為直接獲取到了List ,把loadmore的widget新增進去就行了

這裡就完成了第一部,在呼叫方不發生變化的情況下,我們獲取ListView,並且在底部新增了一個loadmore 的 widget


構造widget

這裡要思考了,我們一共需要幾種狀態
一個是預設時的狀態,這個基本很難見到,也就是空閒狀態
一個是載入中
一個是載入失敗
一個是沒更多的資料

到底這種狀態,因為控制元件理論上不應該控制資料,所以必須由外部傳入
其他的狀態包含在內部

enum LoadMoreStatus {  
/// 空閒中,表示當前等待載入  
///  
/// wait for loading  
idle,  
  
/// 重新整理中,不應該繼續載入,等待future返回  
///  
/// the view is loading  
loading,  
  
/// 重新整理失敗,重新整理失敗,這時需要點選才能重新整理  
///  
/// loading fail, need tap view to loading  
fail,  
  
/// 沒有更多,沒有更多資料了,這個狀態不觸發任何條件  
///  
/// not have more data  
nomore,  
}  
複製程式碼

這裡寫一個enum用於標示狀態


然後就是構建widget了
這裡我們根據一些方法得到狀態,並且構建一個StatefulWidget返回
這裡之所以這麼做,是因為如果返回是無狀態的Widget,則二次滾動到底時,不會再次自動觸發build方法

Widget _buildLoadMoreView() {  
if (widget.isFinish == true) {  
this.status = LoadMoreStatus.nomore;  
} else {  
if (this.status == LoadMoreStatus.nomore) {  
this.status = LoadMoreStatus.idle;  
}  
}  
return NotificationListener<_RetryNotify>(  
child: NotificationListener<_BuildNotify>(  
child: DefaultLoadMoreView(  
status: status,  
delegate: loadMoreDelegate,  
textBuilder: widget.textBuilder,  
),  
onNotification: _onLoadMoreBuild,  
),  
onNotification: _onRetry,  
);  
}  
複製程式碼

這裡之所以有兩個NotifycationListener是用於捕捉內部返回的兩種動作,一個是自動重新整理的動作
一個是點選重試的動作

觸發動作後,我這裡就會修改this.status setState,這樣就會觸發loadmorev View的狀態變化

這裡應用到了一個Flutter的機制,Notification機制,Widget內部通知,外部父節點NotificationListener進行捕獲,然後根據捕獲時的返回值決定是否繼續傳遞.


DefaultLoadMoreView是一個StatefulWidget,在內部定義了點選事件和載入事件

因為一旦顯示就自動構建,所以內部會根據狀態,只有當idle狀態時會傳遞出一個載入的通知,然後上層的Widget獲取Notify後,修改狀態為loading,並調取載入資料的藉口

同理,當載入狀態為錯誤時,內部就不會丟擲notify了,除非在LoadMore的呼叫方修改finish狀態,否則理論上widget就不會再變化,不論滾動與否,這時需要使用者主動點選載入重試,這裡在點選事件中丟擲重試的通知,外部載入

image.png

這裡之所以有一個延時,目的是為了防止setState太頻繁造成介面不變化的問題,理論上這裡大於16ms就可以了


然後我們回到捕獲處

image.png

後記

總體程式碼只有300行,可以在pub裡直接使用,目前最新版本為0.1.1
pub地址 pub國內映象
github

歡迎issue 歡迎star

相關文章