flutter從零開始搭建系列:
專案還是在原來的基礎上搭建,具體的可以看上面的連線
這次,我們來介紹下網路請求,並且將請求到的資料設定到ListView列表中。老規矩,先來看下效果圖
頁面看起來不錯吧,在動手之前還是得說一下,首頁資料來自wanandroid提供,畢竟用了別人的東西就得標明。
實戰
flutter請求網路有兩種,一種是http請求,一種是HttpClient請求,下面來分別來使用一下。
http方式
在使用http方式請求網路時,需要匯入http包
//匯入網路請求相關的包
import 'package:http/http.dart' as http;
void _pullNet() {
http.get("http://www.wanandroid.com/project/list/1/json?cid=1")
.then((http.Response response) {
var convertDataToJson = JSON.decode(response.body);
convertDataToJson = convertDataToJson["data"]["datas"];
//列印請求的結果
print(convertDataToJson);
//更新資料
setState(() {
data = convertDataToJson;
});
});
}
複製程式碼
httpClient方式
需要匯入httpClient包
import 'dart:io';
void _httpClient() async {
var responseBody;
var httpClient = new HttpClient();
var request = await httpClient.getUrl(
Uri.parse("http://www.wanandroid.com/project/list/1/json?cid=1"));
var response = await request.close();
//判斷是否請求成功
if (response.statusCode == 200) {
//拿到請求的資料
responseBody = await response.transform(utf8.decoder).join();
//解析json,拿到對應的jsonArray資料
var convertDataToJson = jsonDecode(responseBody)["data"]["datas"];
//更新資料
setState(() {
data = convertDataToJson;
});
} else {
print("error");
}
}
複製程式碼
知道了網路請求的概念,那麼,我們先來寫下介面
ListView介面佈局
開啟HomePage
,
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return new HomeState();
}
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
body: new ListView(
children: <Widget>[
_getItem2(),
_getItem2()
]),
);
}
Widget _getItem2() {
return new Card(child: new Padding(
padding: const EdgeInsets.all(10.0), child: _getRowWidget2(),),
elevation: 3.0,
margin: const EdgeInsets.all(10.0),);
}
Widget _getRowWidget2() {
return new Row(children: <Widget>[
new Flexible(
flex: 1,
fit: FlexFit.tight, //和android的weight=1效果一樣
child: new Stack(children: <Widget>[
new Column(children: <Widget>[
new Text("title".trim(),
style: new TextStyle(color: Colors.black, fontSize: 20.0,),
textAlign: TextAlign.left),
new Text("desc", maxLines: 3,)
],)
],)
),
new ClipRect(child: new FadeInImage.assetNetwork(
placeholder: "images/ic_shop_normal.png",
image: "images/ic_shop_normal.png",
width: 50.0,
height: 50.0,
fit: BoxFit.fitWidth,),),
],);
}
複製程式碼
效果如下
ListView感覺看起來像是Android中的ScrollView+LineaLayout.vertical。
flutter的佈局其實是個特別頭疼的問題,widget特別多,沒有android那麼方便,也沒有react-native的flex佈局方便,迷之縮排更讓人想刪除widget都變得特別的困難,所以,在做佈局這部分,我們儘可能的將widget做成分割出來,做成一個個的方法widget,然後組合起來。
資料填充
我們看到ListView接收的是一個widget陣列,後臺返回給我們jsonArray資料的時候,我們完全可以使用map來遍歷資料,然後返回widget給Listview的children。
flutter是有生命週期的,大致生命週期可以分為
- initState : 初始化widget的時候呼叫,只會呼叫一次。
- build : 初始化之後開始繪製介面,當setState觸發的時候會再次被呼叫
- didUpdateWidget : 當觸發setState時,會被呼叫
- dispose : 頁面銷燬的時候呼叫
如果把他們和react-native進行分類看的話,下面是個對比
flutter | react native |
---|---|
initState | Mount等函式 |
didUpdateWidget | update等函式 |
dispose | Unmount等函式 |
所以,我們在請求網路的時候,將資料請求放在initState進行處理,下面貼出程式碼。
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; //匯入網路請求相關的包
class HomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return new HomeState();
}
}
class HomeState extends State<HomePage> {
//資料來源
List data;
@override
void initState() {
// TODO: implement initState
super.initState();
_pullNet();
}
void _pullNet() async {
await http.get("http://www.wanandroid.com/project/list/1/json?cid=1")
.then((http.Response response) {
var convertDataToJson = JSON.decode(response.body);
convertDataToJson = convertDataToJson["data"]["datas"];
print(convertDataToJson);
setState(() {
data = convertDataToJson;
});
});
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
body: new ListView(
children: _getItem() ),
);
}
List<Widget> _getItem() {
return data.map((item) {
return new Card(child: new Padding(
padding: const EdgeInsets.all(10.0), child: _getRowWidget(item),),
elevation: 3.0,
margin: const EdgeInsets.all(10.0),);
}).toList();
}
Widget _getRowWidget(item) {
return new Row(children: <Widget>[
new Flexible(
flex: 1,
fit: FlexFit.tight, //和android的weight=1效果一樣
child: new Stack(children: <Widget>[
new Column(children: <Widget>[
new Text("${item["title"]}".trim(),
style: new TextStyle(color: Colors.black, fontSize: 20.0,),
textAlign: TextAlign.left),
new Text("${item["desc"]}", maxLines: 3,)
],)
],)
),
new ClipRect(child: new FadeInImage.assetNetwork(
placeholder: "images/ic_shop_normal.png",
image: "${item['envelopePic']}",
width: 50.0,
height: 50.0,
fit: BoxFit.fitWidth,),),
],);
}
複製程式碼
然後我們來看下效果圖
相信大家看到了一閃而過的紅色報警圖,雖然不影響最後的顯示效果,但是,我們必須得去處理。
在控制檯中,我看到了這樣的一句異常
The method 'map' was called on null
複製程式碼
看到map我們這才煥然大悟,因為網路請求是非同步的,當前介面因為要執行build介面的繪製,導致我們在_getItem
中map遍歷data資料時是個空值,然後再非同步請求成功後,setState又重新給data賦了值,然後觸發了介面重新繪製,這時候,map遍歷是有值,然後就出現了一下會出現異常,然後又好了的原因。
我們得想個辦法,並且能優雅的去解決這個問題,對了,我們只需要在ListView中對data進行判空處理不就行了嗎,如果為空的話,我們給他設定一個預載入頁面,如果不為空的話,直接就當前現在的這套流程。
ok,思路有了,開始幹吧
直接看build方法進行更改
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
body: new ListView(
children: data != null ? _getItem() : _loading()),
);
}
//預載入佈局
List<Widget> _loading() {
return <Widget>[
new Container(
height: 300.0, child: new Center(child:
new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new CircularProgressIndicator(
strokeWidth: 1.0,),
new Text("正在載入"),
],)),)
];
}
複製程式碼
然後我們再來看看效果圖
下次再見咯!