寫在前面
這類的庫在pub上有很多
我為什麼要自定義呢
首先是專案需要,並且這種庫普適性高,抽取出來今後複用也方便點
另外記錄一下編碼思路,方便後續檢視
使用說明
看看構造方法
一共5個屬性
child是ListView
onLoadMore是載入更多時的回撥,由外部實現
isFinish 載入完成
delegate是一個抽象類
有預設實現, 其中有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
那麼我們接收一個ListView,然後判斷其中的delegate型別,然後分別進行處理不就可以了嗎
這裡我們分別處理,一個是增加一個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就不會再變化,不論滾動與否,這時需要使用者主動點選載入重試,這裡在點選事件中丟擲重試的通知,外部載入
這裡之所以有一個延時,目的是為了防止setState太頻繁造成介面不變化的問題,理論上這裡大於16ms就可以了
然後我們回到捕獲處
後記
總體程式碼只有300行,可以在pub裡直接使用,目前最新版本為0.1.1
pub地址 pub國內映象
github
歡迎issue 歡迎star