在開發的過程當中,常常要用到一個具備下拉刷新和上拉加載更多功能的listview ,代碼的實現思路基本是差很少的。因此有必要封裝一個通用的listview,方便使用。git
目標:外部使用BaseListView的時候,只須要傳入一個頁面請求的操做和item構造的方法就可使用。github
將頁面請求的方法定義爲PageRequest,將構造子項的方法定義爲ItemBuilder。 好比下面,PageRequest的返回值是列表數據的future,參數值是當前分頁和每頁頁數。在BaseListView中定義一個 PageRequest的變量給外面賦值,而後就能夠經過變量調用外部的異步操做。 ItemBuilder主要是提供給外部進行自定義構造子項,參數是數據源list和當前位置position。 根據須要能夠定義更多的typedef,這裏就只定義這兩個。bash
//類型定義
typedef Future<List<T>> PageRequest<T>(int page, int pageSize);
typedef Widget ItemBuilder<T>(List<T> list, int position);
複製代碼
這個以前已經實現過,能夠看:github.com/LXD31256949…異步
ListView中有一個ScrollController類型的參數,能夠利用controller來監聽listview的滑動狀態,' 當滑動到底部的時候,能夠loadmore操做async
ListView({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
List<Widget> children = const <Widget>[],
int semanticChildCount,
})
複製代碼
/**這部分代碼主要是設置滑動監聽,滑動到距離底部100單位的時候,開始進行loadmore操做
若是controller.position.pixels==controller.position.maxScrollExtent再去
進行loadmore操做的話,實際的顯示和操做會有點奇怪,因此這裏設置距離底部100
*/
controller = new ScrollController();
controller.addListener(() {
if (controller.position.pixels >=
controller.position.maxScrollExtent - 100) {
if (!isLoading) {
isLoading = true;
loadmore();
}
}
});
複製代碼
/**
* 構造FutureBuilder
*/
FutureBuilder<List<T>> buildFutureBuilder() {
return new FutureBuilder<List<T>>(
builder: (context, AsyncSnapshot<List<T>> async) {
if (async.connectionState == ConnectionState.active ||
async.connectionState == ConnectionState.waiting) {
isLoading = true;
return new Center(
child: new CircularProgressIndicator(),
);
}
if (async.connectionState == ConnectionState.done) {
isLoading = false;
if (async.hasError) {
//有錯誤的時候
return new RetryItem(() {
refresh();
});
} else if (!async.hasData) {
//返回值爲空的時候
return new EmptyItem(() {
refresh();
});
} else if (async.hasData) {
//若是是刷新的操做
if (widget.page == 0) {
_list.addAll(async.data);
}
if (widget.total > 0 && widget.total <= _list.length) {
widget.enableLoadmore = false;
} else {
widget.enableLoadmore = true;
}
debugPrint(
"loadData hasData:page:${widget.page},pageSize:${widget.pageSize},list:${_list.length}");
//計算最終的list長度
int length = _list.length + (widget.hasHeader ? 1 : 0);
return new RefreshIndicator(
child: new ListView.separated(
physics: AlwaysScrollableScrollPhysics(),
controller: widget.enableLoadmore ? controller : null,
itemBuilder: (context, index) {
// TODO:頭部的更新,可能要放在外面,放在裏面的話也行,不過要封裝獲取頭部future的邏輯,而後提供一個外部builder給外部進行構造
// 目前須要在外面判斷position是否爲0去構造頭部
// if (widget.hasHeader && index == 0 && widget.header != null) {
// return widget.header;
// }
//能夠加載更多的時候,最後一個item顯示菊花
if (widget.enableLoadmore && index == length) {
return new LoadMoreItem();
}
return widget.itemBuilder(_list, index);
},
itemCount: length + (widget.enableLoadmore ? 1 : 0),
separatorBuilder: (BuildContext context, int index) {
return new Divider();
},
),
onRefresh: refresh);
}
}
},
future: future,
);
}
複製代碼
下面是跟獲取數據有關的幾個方法:loadmore(),refresh(),loadData()。 loadData()會調用以前定義的頁面請求PageRequest方法ide
Future refresh() async {
debugPrint("loadData:refresh,list:${_list.length}");
if (!widget.enableRefresh) {
return;
}
if (isLoading) {
return;
}
_list.clear();
setState(() {
isLoading = true;
widget.page = 0;
future = loadData(widget.page, widget.pageSize);
futureBuilder = buildFutureBuilder();
});
}
void loadmore() async {
debugPrint("loadData:loadmore,list:${_list.length}");
loadData(++widget.page, widget.pageSize).then((List<T> data) {
setState(() {
isLoading = false;
_list.addAll(data);
futureBuilder = buildFutureBuilder();
});
});
}
Future<List<T>> loadData(int page, int pageSize) async {
debugPrint("loadData:page:$page,pageSize:$pageSize,list:${_list.length}");
return await widget.pageRequest(page, pageSize);
}
複製代碼