APP中最常见的就是列表页面,上拉加载更多,下拉刷新,在Flutter
中 ListView
是最常用的可滚动组件之一,这里我主要使用ListView
实现列表加载,并配合RefreshIndicator
组件实现下拉刷新;还会使用到ListView的嵌套使用等。
创建 Stateful Widgets
import 'dart:convert';
import 'package:app/common/httpUtil.dart';
import 'package:app/common/toast.dart';
import 'package:app/api/Api.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
class Questionnaire extends StatefulWidget {
@override
_QuestionnaireState createState() => _QuestionnaireState();
}
class _QuestionnaireState extends State<Questionnaire> {
// 列表视图(`ListView`)中要显示的数据。
List questionnaireList = new List();
ScrollController _scrollController = new ScrollController();
bool isLoading = true;
// 总页数
int totalPages = 1;
// 当前页数
int pageno = 0;
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("问卷调查"),
),
body: Container(
child: _buildList(),
),
resizeToAvoidBottomPadding: false,
);
}
}
使用dio获取数据
void _getMoreData() async {
if (isLoading) {
try {
// size每页数据条数、start起始页 0,1,2,...
String url = "/xxxx/xxxlist.json?size=10&start=" + pageno.toString();
// print('接口 pageno:' + pageno.toString());
Response response = await dio.get(url);
// print(response);
setState(() {
// 处理返回数据
// 总页数
totalPages = response.data['totalPages'];
// print(totalPages);
questionnaireList.addAll(response.data['content']);
// print(questionnaireList);
});
} on DioError catch (e) {
//catch 提示
print('catch 提示: ' + e.toString());
if (e.response != null) {
print(e.response.data);
dynamic rtn = jsonDecode(e.response.data.toString()); // 解析接口返回的json数据
// print(rtn['status']);
if (rtn['status'] == 401) {
autoLogin().then((val) => initState()); // 自动登录后 调用initState更新页面
}
} else {
showToast("数据加载失败");
print(e.request);
print(e.message);
}
} finally {
setState(() {
isLoading = false;
});
}
}
}
以上使用的dio是https://segmentfault.com/a/1190000021567794#item-2-1 这篇文章提到的创建了一个全局共用的dio。
下拉刷新
build中body改为包裹一层RefreshIndicator组件,onRefresh
为重新获取数据的方法。
body: RefreshIndicator(
onRefresh: _onRefresh,
child: _buildList(),
),
/*
* 下拉刷新方法
*/
Future<Null> _onRefresh() async {
questionnaireList.clear();
setState(() {
isLoading = true;
});
_getMoreData();
}
上拉加载更多
ListView
支持scrollController
事件绑定,当用户在ListView
中滑动时,会出发scrollController
事件。scrollController
组件:scrollController
是一个滑动监听组件,这里我们用来控制何时加载数据。
在scrollController
中增加监听事件addListener
,判断滑动页面时,如果没有拉到底部,并且数据不是正在加载中状态,是否是最后一页,等条件逻辑,来决定是否加载更多数据,并通过setState来更新视图。
@override
void initState() {
setState(() {
isLoading = true;
});
this._getMoreData();
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
// print('滑动到了最底部');
if (pageno < totalPages - 1) {
pageno++;
// print('加载更多 pageno:' + pageno.toString());
// 加载更多
setState(() {
isLoading = true;
});
_getMoreData();
} else {
// 没有更多了
showToast("没有更多了");
}
}
});
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
以上使用到的showToast
是Flutter 常用的提示框showToast、showLoading、showConfirmDialog写在lib/common/toast.dart
中的全局共用方法。
ListView 显示
在ListView builder
中,我们使用条件判断来让最后一行显示暂无数据、加载中动画(ProgressBar)、数据列表。
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isLoading ? 1.0 : 00,
child: new CircularProgressIndicator(),
),
),
);
}
Widget _buildList() {
return ListView.builder(
//itemCount +1 为了显示加载中progressbar和暂无数据
itemCount: questionnaireList.length + 1,
itemBuilder: (context, index) {
if (questionnaireList.length == 0 && isLoading) {
// 加载中
return _buildProgressIndicator();
} else if (questionnaireList.length == 0 && !isLoading) {
// 暂无数据
return Padding(
padding: const EdgeInsets.all(18.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Text('暂无数据!'),
],
),
);
} else if (questionnaireList.length == index && !isLoading) {
// 多加的那个1,其实没数据应该不显示,去掉这个显示会报错
return _buildProgressIndicator();
} else {
// 列表显示
return new GestureDetector(
// 列表的点击tap事件
onTap: () => _handleTapToDetail(conductStatus),
child: Card(
child: _buildContainer(),
),
);
}
},
controller: _scrollController,
);
}
_handleTapToDetail(String conductStatus){
// 列表的点击tap事件 进入详情或者其他操作等逻辑
}
_buildContainer(){
// 具体显示列表内容的布局,此处略
}
以上讲述了用ListView和RefreshIndicator组件实现列表页面的上拉加载更多下拉刷新。
ListView 嵌套 ListView 滚动问题
上述的列表是以问卷调查为例,那么这里使用到的ListView
嵌套 ListView
的情况就是问卷的题目列表的展示,大家平时都做过问卷、试卷,知道题目列表包含:大题的题目,小题的题目,小题的选项等,做这种布局,就用到了ListView 嵌套。
最重要的是最外层ListView
设置controller: _scrollController
,并且要设置shrinkWrap: true
,根据子widget
的总长度来设置ListView
的长度。然后内里嵌套的所有子ListView
,设置shrinkWrap: true
根据子widget
的总长度来设置ListView
的长度,并且设置 physics: new NeverScrollableScrollPhysics()
禁用问题列表子组件的滚动事件。
new ListView(
shrinkWrap: true, //是否根据子widget的总长度来设置ListView的长度,默认值为false
controller: _scrollController,
children: <Widget>[
new Text(
"title",
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: TextStyle(
color: Color.fromRGBO(0, 0, 0, 1.0), //opacity:不透明度
fontFamily: 'PingFangBold',
fontSize: 15.0,
),
),
Container(
child: _buildList(),
),
],
),
Widget _buildList() {
return ListView.builder(
shrinkWrap: true, //是否根据子widget的总长度来设置ListView的长度,默认值为false
physics: new NeverScrollableScrollPhysics(), // 禁用问题列表子组件的滚动事件
//itemCount +1 为了显示加载中和暂无数据progressbar
itemCount: dataList.length + 1,
itemBuilder: (context, index) {
// 逻辑与上述相似,不再重复,省略了
},
);
}
参考资料
ListView class: A scrollable list of widgets arranged linearly.
ListView
Flutter下拉刷新,上拉加载更多数据
Flutter ListView 分页加载更多效果
Flutter 问题解决总结:ScrollView 嵌套 ListView 滚动问题
flutter禁用滚动事件
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。