之前学习中使用的都是StatelessWidget,是不可变的,这意味着它们的属性不能改变。而当我们需要一个动态的属性可变的控件,就需要用到StatefulWidget。
StatefulWidget持有的状态可能在 widget 生命周期中发生变化,实现一个 stateful widget 至少需要两个类:1)一个 StatefulWidget 类;2)一个 State 类,StatefulWidget 类本身是不变的,但是 State 类在 widget 生命周期中始终存在。
在这一步,你将添加一个 StatefulWidget-ListViewSample,它会创建自己的状态类-_ListViewSampleState,然后你需要将 ListViewSample 内嵌到已有的App里。
先导入网络请求的包:
import 'dart:io';
import 'dart:convert';
列表展示及网络请求核心代码:
//StatefulWidget申明
class ListViewSample extends StatefulWidget {
@override
State createState() {
return new _ListViewSampleState();
}
}
//StatefulWidget对应的State
class _ListViewSampleState extends State<ListViewSample> {
List _listItems; //私有的list数据
@override
void initState() {
super.initState();
//初始化时即进行数据请求
getNewMovie();
}
//请求豆瓣当前热门电影
getNewMovie() async {
var httpClient = new HttpClient();
//网络请求,{}里可以添加参数
var uri = new Uri.http('api.douban.com', '/v2/movie/in_theaters', {});
//flutter是单线程,用httpclient进行网络请求时要用await关键字,并且此时返回的都是Future对象s
var request = await httpClient.getUrl(uri);
var response = await request.close();
if (response.statusCode == 200) {
var responseBody = await response.transform(utf8.decoder).join();
//注意很多教程里json转换用的是JSON.decode,然而在18年底的一个版本中,把常量都改为小写了。现在都是用json.decode
Map<String, dynamic> map = json.decode(responseBody);
print('refresh movie list ');
setState(() {
_listItems = new List();
for (dynamic movie in map['subjects']) {
_listItems.add(movie);
}
});
} else {
print("http error ${response.statusCode}");
}
}
@override
Widget build(BuildContext context) {
//还没请求到数据的时候显示加载中
if (_listItems == null) {
return new Center(
heightFactor: 6,
child: new Text('加载中...'),
);
}
//请求到数据后显示列表
return new Expanded(
child: new ListView.builder(
scrollDirection: Axis.vertical,
itemCount: _listItems.length,
itemBuilder: (context, index) {
return movieItem(context, index);
},
),
);
}
//列表单元的UI封装
Widget movieItem(BuildContext context, int index) {
return new Column(
children: <Widget>[
//分割线
new Divider(),
//包裹一个GestureDetector,响应点击事件
new GestureDetector(
//点击item的时候也再请求数据
onTap: getNewMovie,
//item容器
child: new Container(
padding: const EdgeInsets.all(8.0),
//一级Row布局
child: new Row(
//右边布局顶对齐
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
//显示电影图片
new Image.network(
_listItems[index]['images']['small'],
width: 100,
),
//右边布局
new Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
//电影名
new Container(
padding: EdgeInsets.all(8.0),
width: 200,
child: new Text(
_listItems[index]['title'],
style: new TextStyle(
color: Colors.blueAccent,
),
overflow: TextOverflow.clip,
textAlign: TextAlign.left,
),
),
//评分
new Container(
padding: EdgeInsets.all(8.0),
width: 200,
child: new Text(
'评分:' +
_listItems[index]['rating']['average'].toString(),
style: new TextStyle(
color: Colors.orange,
),
overflow: TextOverflow.clip,
textAlign: TextAlign.left,
),
),
//导演
new Container(
padding: EdgeInsets.all(8.0),
width: 200,
child: new Text(
'导演:' + _listItems[index]['directors'][0]['name'],
style: new TextStyle(
color: Colors.blueAccent,
),
overflow: TextOverflow.clip,
textAlign: TextAlign.left,
),
),
],
),
],
),
),
),
],
);
}
}
可以看到在Flutter中UI的嵌套层级非常深,而在Android中如果用约束布局,一两层就可以搞定了,这方面是很不习惯。但是代码量来说Flutter是远少于Android的,不需要单独写xml文件,网络请求库的使用非常简单,列表不需要写Adapter那么麻烦。
Item单元的代码中我加了GestureDetector,这是为了添加点击事件,在点击时刷新数据。这种在控件内部修改state的方式比较简单,如果是在控件外修改state就比较麻烦了。
这里的网络请求用的是自带的httpclient,代码比较简单,不需要进行一大堆设置就能先跑起来,要注意Dart是单线程的,所有耗时任务都要加入async关键词放入延时任务里,具体参考 异步async、await和Future的使用技巧
网友评论