项目还是在原来的基础上搭建,具体的可以看上面的连接
这次,我们来介绍下网络请求,并且将请求到的数据设置到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("正在加载"),
],)),)
];
}
然后我们再来看看效果图
下次再见咯!