??????小菜前段時(shí)間整理了兩種 ListView 的異步加載數(shù)據(jù)時(shí)焕檬,下拉刷新與上滑加載更多的方式,每種方式都有自己的優(yōu)勢(shì)澳泵,網(wǎng)上也有很多大神講解過(guò) ListView 數(shù)據(jù)流的種種處理方式实愚,小菜根據(jù)實(shí)際遇到的情況整理一下嘗試的第三種方案。
RefreshIndicator 下拉刷新
??????Flutter 提供了自帶刷新效果的 RefreshIndicator兔辅,這也是網(wǎng)上大神們用的最多的 Widget 之一腊敲,使用方式也很簡(jiǎn)單,RefreshIndicator 中提供了一個(gè)刷新的回調(diào)入口 onRefresh维苔,僅需在該回調(diào)接口中處理數(shù)據(jù)請(qǐng)求即可碰辅,如下:
// 刷新時(shí)數(shù)據(jù)請(qǐng)求
Future<Null> _loadRefresh() async {
await Future.delayed(Duration(seconds: 2), () {
setState(() {
dataItems.clear();
lastFileID = '0';
rowNumber = 0;
_getNewsData(lastFileID, rowNumber);
return null;
});
});
}
// 請(qǐng)求接口整合數(shù)據(jù)
_getNewsData(var lastID, var rowNum) async {
await http
.get(
'https://XXX.../getArticles?...&lastFileID=${lastID}&rowNumber=${rowNum}')
.then((response) {
if (response.statusCode == 200) {
var jsonRes = json.decode(response.body);
newsListBean = NewsListBean(jsonRes);
if (lastID == '0' && rowNum == 0 && dataItems != null) {
dataItems.clear();
}
setState(() {
if (newsListBean != null &&
newsListBean.list != null &&
newsListBean.list.length > 0) {
for (int i = 0; i < newsListBean.list.length; i++) {
dataItems.add(newsListBean.list[i]);
}
lastFileID = newsListBean.list[newsListBean.list.length - 1].fileID.toString();
rowNumber += newsListBean.list.length;
} else {}
});
}
});
}
// 綁定列表數(shù)據(jù)
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("第三種加載方式"),
),
body: new RefreshIndicator(
child: ListView.builder(
itemCount: items.length,
itemBuilder: buildListData(context, dataItems[index])
),
onRefresh: _loadRefresh, // 刷新回調(diào)
));
}
ScrollController 上滑動(dòng)加載更多
??????至此,列表的下拉刷新就完成了介时,接下來(lái)處理【上滑加載更多】没宾,這時(shí)我們可以借助 ScrollController,用來(lái)監(jiān)聽列表是否滑動(dòng)到底部沸柔,主要分兩步:
- 初始化時(shí)添加監(jiān)聽事件,判斷是否滑動(dòng)到最底部会钝;
- ListView 中添加監(jiān)聽方法乱凿。
ScrollController _scrollController = new ScrollController();
@override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_getMoreData(); // 當(dāng)滑到最底部時(shí)調(diào)用
}
});
_getMoreData(); // 數(shù)據(jù)初始化
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("第三種加載方式"),
),
body: ListView.builder(
itemCount: items.length,
itemBuilder: buildListData(context, dataItems[index]),
controller: _scrollController,
));
}
??????至此顽素,列表的下拉刷新與上滑加載更多就基本完成了;接下來(lái)需要將兩種合并使用徒蟆,也很簡(jiǎn)單,如下:
body: new Padding(
padding: EdgeInsets.all(2.0),
child: RefreshIndicator(
onRefresh: _loadRefresh,
child: ListView.builder(
itemCount: dataItems.length,
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
return buildListData(context, dataItems[index]);
},
controller: _scrollController,
)));
??????Tips: 注意處理好數(shù)據(jù)接口請(qǐng)求內(nèi)容全蝶。
小優(yōu)化
優(yōu)化一:【上滑加載更多】添加動(dòng)畫效果
- 添加一個(gè)加載更多的布局 Widget;
- 在 itemCount 中將 item 個(gè)數(shù) +1寺枉;
- 添加監(jiān)聽判斷抑淫,當(dāng)滑到最后一個(gè) item 時(shí)展示加載更多到布局 Widget。
body: new Padding(
padding: EdgeInsets.all(2.0),
child: RefreshIndicator(
onRefresh: _loadRefresh,
child: ListView.builder(
itemCount: dataItems.length + 1,
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
if (index == dataItems.length) {
return _buildProgressIndicator();
} else {
return buildListData(context, dataItems[index]);
}
},
controller: _scrollController,
)));
// 加載更多 Widget
Widget _buildProgressIndicator() {
return new Padding(
padding: EdgeInsets.fromLTRB(0.0, 14.0, 0.0, 14.0),
child: new Opacity(
opacity: isShowLoading ? 1.0 : 0.0,
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new SpinKitChasingDots(color: Colors.blueAccent, size: 26.0),
new Padding(
padding: EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 0.0),
child: new Text('正在加載中...'))
],
)));
}
優(yōu)化二:第一次初始化加載數(shù)據(jù)時(shí)添加 loading 動(dòng)畫
??????RefreshIndicator 中自帶刷新的動(dòng)畫始苇,所以小菜只是在第一次加載數(shù)據(jù)時(shí)添加一個(gè) loading 動(dòng)畫筐喳,小菜只是填了一個(gè)小小的狀態(tài)判斷函喉,如下包括異常情況下的失敗頁(yè)荣月。
Widget childWidget() {
Widget childWidget;
if (newsListBean != null &&
(newsListBean.success != null && !newsListBean.success)) {
isFirstLoading = false;
childWidget = new Stack(children: <Widget>[
new Padding(
padding: new EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 100.0),
child: new Center(
child: Image.asset( 'images/icon_wrong.jpg', width: 120.0, height: 120.0, ))),
new Padding(
padding: new EdgeInsets.fromLTRB(0.0, 100.0, 0.0, 0.0),
child: new Center(
child: new Text(
'抱歉!暫無(wú)內(nèi)容哦~',
style: new TextStyle(fontSize: 18.0, color: Colors.blue),
)))
]);
} else if (dataItems != null && dataItems.length != 0) {
isFirstLoading = false;
childWidget = new Padding(
padding: EdgeInsets.all(2.0),
child: RefreshIndicator(
onRefresh: _loadRefresh,
child: ListView.builder(
itemCount: dataItems.length + 1,
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
if (index == dataItems.length) {
return _buildProgressIndicator();
} else {
return buildListData(context, dataItems[index]);
}
},
controller: _scrollController,
)));
} else {
if (isFirstLoading) { // 只有在第一次加載數(shù)據(jù)時(shí)才會(huì)展示自定義 loading
childWidget = new Center(
child: new Card(
child: new Stack(children: <Widget>[
new Padding(
padding: new EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 35.0),
child: new Center(
child: SpinKitFadingCircle( color: Colors.blueAccent, size: 30.0, ))),
new Padding(
padding: new EdgeInsets.fromLTRB(0.0, 35.0, 0.0, 0.0),
child: new Center(
child: new Text('正在加載中捐下,莫著急哦~'),
))
])),
);
} else {}
}
return childWidget;
}
優(yōu)化三:借助 Future.delayed() 進(jìn)行延遲加載坷襟,使數(shù)據(jù)請(qǐng)求銜接性更好咽白。
_getMoreData() async {
if (!isShowLoading) {
setState(() {
isShowLoading = true;
});
await Future.delayed(Duration(seconds: 2), () {
setState(() {
_getNewsData(lastFileID, rowNumber);
isShowLoading = false;
return null;
});
});
}
}
??????小菜剛接觸 Flutter 時(shí)間不長(zhǎng),還有很多不清楚和不理解的地方晶框,如果有不對(duì)的地方還希望多多指教。
來(lái)源:阿策小和尚