小菜前兩天再學 ListView 時,整理了一下在列表中展示多種不同 item 樣式,今天繼續深入學習異步請求數據并加載新聞列表以及初始進入頁面的 loading 等小知識點。暫時還沒有學習下拉刷新與上劃加載更多。
一. 異步請求數據 async + wait
小菜在前一篇關于網絡請求小博客中整理過基本的異步使用方法;小菜在學習中發現有兩個小地方需要注意一下:
- 使用 StatefulWidget 時,一定一定不要忘記 setState(() {});
小菜準備在剛進入頁面時,開啟異步請求數據,可以在 initState() 中進行操作,如下:
@overridevoid initState() {getNewsData();}
二. json 數據解析
請求到數據之后必然得需要 json 解析,首先需要引入 import dart:convert show json; 之后,小菜主要是使用 response.body 中數據進行處理,json.decode(response.body); 將 json 轉為標準的 key-value 格式;最讓小菜頭疼的是實體類轉換,實體類的定義一定要全面且字段格式正確,不然解析出問題不容易定位。(請諒解:小菜的測試 url 無法公布)
getNewsData() async {await http .get(https://...?sid=xkycs&cid=${cid}&rowNumber=${rowNumber}) .then((response) {if (response.statusCode == 200) { var jsonRes = json.decode(response.body); newsListBean = NewsListBean(jsonRes); setState(() { for (int i = 0; i < newsListBean.list.length; i++) { print("==77==${newsListBean.list[i].title}"); dataItems.add(newsListBean.list[i]); } });}});}
小菜多帶帶為實體類區分為一個新的 .dart 文件,需要注意的是,若實體類中有列表,一定要注意判空,如下:
class NewsListBean {List
list;int rowNumber;bool success;String msg;NewsListBean(jsonRes) {rowNumber = jsonRes[rowNumber];success = jsonRes[success];msg = jsonRes[msg];list = [];if (jsonRes[list] != null) { for (var dataItem in jsonRes[list]) { list.add(new ListBean(dataItem)); }}}}
class ListBean {
int fileID;
String title;
int version;
String abstractX;
String publishTime;
String realPublishTime;
int articleType;
String pic3;
String pic2;
String pic1;
int bigPic;
String tag;
String contentUrl;
String videoImgUrl;
ListBean(jsonRes) {
fileID = jsonRes[fileID];
title = jsonRes[title];
version = jsonRes[version];
abstractX = jsonRes[abstract];
publishTime = jsonRes[publishTime];
realPublishTime = jsonRes[realPublishTime];
articleType = jsonRes[articleType];
pic3 = jsonRes[pic3];
pic2 = jsonRes[pic2];
pic1 = jsonRes[pic1];
bigPic = jsonRes[bigPic];
tag = jsonRes[tag];
contentUrl = jsonRes[contentUrl];
videoImgUrl = jsonRes[videoImgUrl];
}
}
### 三. 列表加載數據 小菜每次寫 item 時都會想到 Flutter 中一切都是 Widget 的重要性,小菜建議很多公共的或重復的 Widget 完全可以提取成統一的 Widget,即方便管理也會大幅度減少代碼量。
Widget buildListData(BuildContext context, ListBean listBean) {
Widget itemWidget;
if (listBean != null) {
switch (listBean.articleType) {
case 1:
itemWidget = new Card(
child: new Container(
child: new Column(
children:
new Row(
children:
new Expanded(
child: new Container(
padding: const EdgeInsets.fromLTRB(3.0, 6.0, 3.0, 0.0),
child: new Image.network( listBean.pic1, fit: BoxFit.cover, ),
height: 100.0,
),
flex: 1,
),
new Expanded(
child: new Container(
padding: const EdgeInsets.fromLTRB(3.0, 6.0, 3.0, 0.0),
child: new Image.network( listBean.pic2, fit: BoxFit.cover, ),
height: 100.0,
),
flex: 1,
),
new Expanded(
child: new Container(
padding: const EdgeInsets.fromLTRB(3.0, 6.0, 3.0, 0.0),
child: new Image.network( listBean.pic3, fit: BoxFit.cover, ),
height: 100.0,
),
flex: 1,
),
],
),
new Padding( padding: new EdgeInsets.fromLTRB(8.0, 6.0, 0.0, 6.0), child: botRow(listBean), ),
],
),
),
);
break;
case 2:
itemWidget = new Card(
child: new Column(
children:
new Row(
children:
new Expanded(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children:
new Padding( padding: new EdgeInsets.fromLTRB(8.0, 6.0, 0.0, 3.0), child: new Text(listBean.title), ),
new Padding( padding: new EdgeInsets.fromLTRB(8.0, 6.0, 0.0, 3.0), child: new Text( listBean.abstractX, style: new TextStyle(fontSize: 14.0), ), ),
new Padding( padding: new EdgeInsets.fromLTRB(8.0, 6.0, 0.0, 3.0), child: botRow(listBean), ),
],
),
flex: 2,
),
new Expanded(
child: new Container(
padding: const EdgeInsets.fromLTRB(3.0, 6.0, 3.0, 6.0),
child: new Image.network( listBean.pic1, fit: BoxFit.cover, ),
height: 100.0,
),
flex: 1,
),
],
),
],
),
);
break;
default:
Widget absWi;
if (listBean.abstractX == null || listBean.abstractX.length == 0) {
absWi = new Container( width: 0.0, height: 0.0, );
} else {
absWi = new Padding( padding: new EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 0.0),child: new Text( listBean.abstractX, style: new TextStyle(fontSize: 14.0), ), );
}
itemWidget = new Card(
child: new Padding(
padding: new EdgeInsets.all(10.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children:
new Text( listBean.title, style: new TextStyle(fontSize: 17.0), ),
absWi,
new Padding( padding: new EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 0.0), child: botRow(listBean), ),
],
),
),
);
break;
}
return itemWidget;
}
}
// 底部時間和音視頻顯隱性
Widget botRow(ListBean listBean) {
Widget videoWi;
if (listBean.videoImgUrl == null || listBean.videoImgUrl.length == 0) {
videoWi = new Container( width: 0.0, height: 0.0, );
} else {
videoWi = new Padding(
padding: new EdgeInsets.fromLTRB(0.0, 0.0, 6.0, 0.0),
child: new Row(
children:
new Padding(
padding: new EdgeInsets.fromLTRB(0.0, 2.0, 6.0, 0.0),
child: new Center( child: new Icon( Icons.queue_music, size: 13.0, color: Colors.blueAccent, ), ),
),
new Text( 音頻, style: new TextStyle(fontSize: 13.0), ),
],
),
);
}
return new Padding(
padding: new EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 0.0),
child: new Row(
children:
videoWi,
new Padding(
padding: new EdgeInsets.fromLTRB(0.0, 0.0, 6.0, 0.0),
child: new Icon( Icons.access_time, size: 13.0, color: Colors.blueAccent,),
),
new Text( listBean.publishTime, style: new TextStyle(fontSize: 13.0),),
],
),
);
}
小菜處理成在沒有加載出列表數據之前添加一個 loading 提醒,如下:
Widget childWidget() {
Widget childWidget;
if (dataItems != null && dataItems.length != 0) {
childWidget = new Padding(
padding: EdgeInsets.all(6.0),
child: new ListView.builder(
itemCount: dataItems.length,
itemBuilder: (context, item) {
return buildListData(context, dataItems[item]);
},
),
);
} else {
childWidget = new Stack(
children:
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;
}
![](https://s4.51cto.com/images/blog/202111/22090007_619aeb97b858131176.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)![](https://s4.51cto.com/images/blog/202111/22090007_619aeb97acdd052317.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)![](https://s4.51cto.com/images/blog/202111/22090007_619aeb97b7b5778840.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)### 四. loading 提醒 小菜在加載數據之后發現,網絡狀況不佳或數據量大時都應有 loading 提醒,盡量給用戶一個良好的體驗。 小菜偷了個懶,借用一個三方庫 **[flutter_spinkit](https://pub.flutter-io.cn/packages/flutter_spinkit)**,這個 loading 庫集成簡單而且效果多樣,基本包含日常中常見的樣式。![](https://s4.51cto.com/images/blog/202111/22090007_619aeb9783be457528.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)####集成步驟:1. pubspec.yaml 中添加 **flutter_spinkit: "^2.1.0"**;2. 在相應的 .dart 文件中添加引用 **import package:flutter_spinkit/flutter_spinkit.dart;**3. 添加需要展示的樣式:SpinKit + Wave() 方式,同時與官網的使用有點區別,官網中用 width 和 height 來設置寬高,但是小菜在測試過程中,源碼中提供了 size 方法,一個屬性即可。![](https://s4.51cto.com/images/blog/202111/22090007_619aeb97e68722435.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
new Column(
children:
new SpinKitRotatingPlain(color: Colors.blueAccent, size: 30.0,),
new SpinKitRotatingCircle(color: Colors.blueAccent, size: 30.0,),
new SpinKitDoubleBounce(color: Colors.blueAccent, size: 30.0,),
new SpinKitRing(color: Colors.blueAccent, size: 30.0,),
new SpinKitWave(color: Colors.blueAccent, size: 30.0,),
new SpinKitWanderingCubes(color: Colors.blueAccent, size: 30.0,),
new SpinKitFadingCube(color: Colors.blueAccent, size: 30.0,),
new SpinKitFadingFour(color: Colors.blueAccent, size: 30.0,),
new SpinKitPulse(color: Colors.blueAccent, size: 30.0,),
new SpinKitChasingDots(color: Colors.blueAccent, size: 30.0,),
new SpinKitHourGlass(color: Colors.blueAccent, size: 30.0,),
new SpinKitSpinningCircle(color: Colors.blueAccent, size: 30.0,),
],
)
![](https://s4.51cto.com/images/blog/202111/22090007_619aeb97e6cd862733.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)![](https://s4.51cto.com/images/blog/202111/22090008_619aeb9833bea10324.jpg?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)*** 小菜剛接觸 **Flutter** 時間不長,還有很多不清楚和不理解的地方,如果又不對的地方還希望多多指出。> 來源:阿策小和尚