「这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战」
回顾
上一篇我们已经拿到了数据,本章主要讲数据怎么显示。数据显示就不可避免的要提到异步加载以及数据的转换
json转换成字典
Flutter
中json数据转换成字典,可以使用json.decode
import 'dart:convert';
json.decode(response.body);
复制代码
字典转换成模型
构造方法如果想要有返回值,此时需要使用工厂方法factory
class ChatModel {
final String? imgUrl;
final String? name;
final String? message;
ChatModel({this.imgUrl, this.name, this.message});
factory ChatModel.fromMap(Map map) {
return ChatModel(
imgUrl: map['imgUrl'], name: map['name'], message: map['message']);
}
}
复制代码
模型数据源
异步加载的数据,拿到所有的数据的返回值,可以使用Future
Future<List<ChatModel>> getData() async {
final url =
Uri.parse('http://rap2api.taobao.org/app/mock/293759/home/chat/list');
var response = await http.get(url);
if (response.statusCode == 200) {
// json转换成字典
final responseBody = json.decode(response.body);
return responseBody['data']
.map<ChatModel>((item) => ChatModel.fromMap(item))
.toList();
//转模型
} else {
throw Exception('statusCode=${response.statusCode}');
}
}
}
复制代码
渲染
有一个专门用来渲染异步加载回来的数据FutureBuilder
,有一个必传参数builder
,command
点击进去文档是一个有返回值有参数的方法typedef AsyncWidgetBuilder<T> = Widget Function(BuildContext context, AsyncSnapshot<T> snapshot);
Container(
child: FutureBuilder(
builder: (BuildContext context, AsyncSnapshot snapshot) {
print('${snapshot.data}');
return Container();
},
future: getData(),
)),
复制代码
打印snapshot.data
的时候,发现这里会走两次,一次页面刚加载的时候没有拿到数据这里打印的是null
,第二次则是拿到数组之后再次刷新数据
snapshot.connectionState
:waiting的时候没有数据,done的时候表明数据已经加载完了。
所以此时我们可以根据这个
connectionState
的状态来判断
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: Text('加载中...'));
}
复制代码
ListView的children
可以使用snapshot.data.map<Widget>((ChatModel item)
数组遍历生成,同时这里介绍一个新的Widget
:ListTile
包含比较简单常用的一些布局
const ListTile({
Key? key,
this.leading,
this.title,
this.subtitle,
this.trailing,
this.isThreeLine = false,
this.dense,
this.visualDensity,
this.shape,
this.contentPadding,
this.enabled = true,
this.onTap,
this.onLongPress,
this.mouseCursor,
this.selected = false,
this.focusColor,
this.hoverColor,
this.focusNode,
this.autofocus = false,
this.tileColor,
this.selectedTileColor,
this.enableFeedback,
this.horizontalTitleGap,
this.minVerticalPadding,
this.minLeadingWidth,
}) : assert(isThreeLine != null),
assert(enabled != null),
assert(selected != null),
assert(autofocus != null),
assert(!isThreeLine || subtitle != null),
super(key: key);
复制代码
这样布局出来的效果就是这样的:
状态保留
上面有一个小问题,每次页面切出去再次切回来的时候,数据都会重新加载。这样的话状态就没有保留。如果想要保留的话需要:
- 继承
AutomaticKeepAliveClientMixin
class _ChatPageState extends State<ChatPage> with AutomaticKeepAliveClientMixin
- 然后实现父类的方法
@override
bool get wantKeepAlive => true;
复制代码
- 在
Widget build(BuildContext context)
中实现super.build(context);
修改Widget树
尽管实现了上面的3部曲,但是切回来的时候还是会重新init,这个是因为在根控制器下每次的页面都是重新生成的,并没有在当前的Widget树中,我们回到RootPage
页面中重新修改一下
- 声明一个
final PageController _pageController = PageController();
body
使用PageView
body: PageView(
children: _pages,
controller: _pageController,
),
复制代码
- 切换页面的时候,
_pageController
同步跳转
setState(() {
_currentIndex = index;
_pageController.jumpToPage(_currentIndex);
});
复制代码
这样每次点击TabbarItem
的时候就能保存住当前页面的状态。但是此时有个小问题,当前的页面可以滑动切屏,但是底部的按钮不会随之联动。这个小问题可以在PageView onPageChanged
中解决:
onPageChanged: (index) {
setState(() {
_currentIndex = index;
});
},
复制代码
或者设置不能拖拽切屏:physics: NeverScrollableScrollPhysics(),
Future
在上面网络请求的时候用到了Future
,那么这个到底是什么?
String _temp = '0';
void main() {
// operatorDemo();
getData();
print('循环之前');
}
getData() {
print('开始了');
Future(() {
for (int i = 0; i < 100; i++) {}
print('循环结束');
});
}
复制代码
经过测试发现使用
Future
修饰的代码块会异步执行,不会卡住当前的线程。如果希望在这个异步任务执行完成之后再操作,需要在Future
前面加上一个await
String _temp = '0';
void main() {
// operatorDemo();
getData();
print('循环之前');
}
getData() async {
print('开始了');
await Future(() {
for (int i = 0; i < 100; i++) {}
print('循环结束');
});
print('await之后的代码');
}
复制代码
完整代码地址: