小菜上次嘗試 ListView 異步加載清單資料時,用了三方庫 flutter_refresh
,這種方式使用很簡單。但清單資料的加載也絕非一種,小菜這次準備用原生嘗試一下。因為種種原因,小菜這次的整理距離上次時間很長,還是應該加強自控力。
小菜這次的清單并沒有單獨處理動畫效果,隻是對資料的重新整理與加載更多進行正常加載進行處理,還需要進一步的學習研究。
ListView + NotificationListener
小菜參考了很多大神的實作方式,發現
NotificationListener很像 Android 的滑動監聽事件,再頂部和底部添加事件處理,內建方式也很簡單。
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('新聞清單'),
elevation: 0.0, // 陰影高度
),
body: new NotificationListener(
onNotification: dataNotification,
child: childWidget(),
),
);
}
問題小結
一:如何區分清單滑動到頂部或底部?
NotificationListener 中可以根據如下狀态進行判斷,并在相應的狀态下進行需要的處理:
- (notification.metrics.extentAfter == 0.0) 為滑動到 底部;
- (notification.metrics.extentBefore == 0.0) 為滑動到 頂部。
bool dataNotification(ScrollNotification notification) {
if (notification is ScrollEndNotification) {
//下滑到最底部
if (notification.metrics.extentAfter == 0.0) {
print('======下滑到最底部======');
loadData();
}
//滑動到最頂部
if (notification.metrics.extentBefore == 0.0) {
print('======滑動到最頂部======');
lastFileID = '0';
rowNumber = 0;
dataItems.clear();
loadData();
}
}
return true;
}
二:監聽的整個過程滑動中多次調用接口?
小菜在測試過程中每次滑動一下清單都會調用一次接口,因為在監聽過程中若不做任何處理隻要清單滑動便會進行監聽,小菜的解決的方式有兩種;
- 監聽滑動到底部再進行業務操作調用接口,如問題一中的判斷;
bool dataNotification(ScrollNotification notification) {
if (notification is ScrollEndNotification) {
//下滑到最底部
if (notification.metrics.extentAfter == 0.0) {
print('======下滑到最底部======');
loadData();
}
}
return true;
}
- 嘗試使用 TrackingScrollController ,對滑動進行監聽,這個類可用于同步兩個或更多個共享單個 TrackingScrollController 的惰性建立的滾動視圖的滾動偏移。它跟蹤最近更新的滾動位置,并将其報告為其初始滾動偏移量。且在非底部時 maxScrollExtent 和 offset 值會相等。使用該類監聽時更靈活,有些操作并非到底部才會進行處理等。
bool dataNotification(ScrollNotification notification) {
if (notification is ScrollUpdateNotification) {
if (_scrollController.mostRecentlyUpdatedPosition.maxScrollExtent >
_scrollController.offset &&
_scrollController.mostRecentlyUpdatedPosition.maxScrollExtent -
_scrollController.offset <= 50) {
loadData();
}
}
return true;
}
三:異常情況處理?
小菜以前對清單的處理隻包括清單資料為 0 時展示 Loading 等待頁,有資料時展示資料清單,但是對于其他異常情況沒有處理,這次特意添加上異常頁面,這僅僅是業務方面的添加,沒有新的技術點。
主要源碼
class LoadMoreState extends State<LoadMorePage> {
var lastFileID = '0';
var rowNumber = 0;
var cid = '29338';
var newsListBean = null;
List<ListBean> dataItems = <ListBean>[];
@override
void initState() {
super.initState();
loadData();
}
// 請求首頁資料
Future<Null> loadData() {
this.isLoading = true;
final Completer<Null> completer = new Completer<Null>();
getNewsData();
completer.complete(null);
return completer.future;
}
getNewsData() async {
await http
.get(
'https://XXX...&lastFileID=${lastFileID}&rowNumber=${rowNumber}')
.then((response) {
if (response.statusCode == 200) {
var jsonRes = json.decode(response.body);
newsListBean = NewsListBean(jsonRes);
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 {}
});
}
});
}
bool dataNotification(ScrollNotification notification) {
if (notification is ScrollEndNotification) {
//下滑到最底部
if (notification.metrics.extentAfter == 0.0) {
print('======下滑到最底部======');
loadData();
}
//滑動到最頂部
if (notification.metrics.extentBefore == 0.0) {
print('======滑動到最頂部======');
lastFileID = '0';
rowNumber = 0;
dataItems.clear();
loadData();
}
}
return true;
}
// 處理清單中是否有資料,對應展示相應頁面
Widget childWidget() {
Widget childWidget;
if (newsListBean != null &&
(newsListBean.success != null && !newsListBean.success)) {
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( '抱歉!暫無内容哦~', style: new TextStyle(fontSize: 18.0, color: Colors.blue), ),
),
),
],
);
} else if (dataItems != null && dataItems.length != 0) {
childWidget = new Padding(
padding: EdgeInsets.all(2.0),
child: new ListView.builder(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(6.0),
itemCount: dataItems == null ? 0 : dataItems.length,
itemBuilder: (context, item) {
return buildListData(context, dataItems[item]);
}),);
} else {
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('正在加載中,莫着急哦~'), ),
),
])),);
}
return childWidget;
}
}
小菜剛接觸 Flutter 時間不長,還有很多不清楚和不了解的地方,如果又不對的地方還希望多多指出。