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("正在加载"), ],)),) ]; } 复制代码
然后我们再来看看效果图