APP中最多见的就是列表页面,上拉加载更多,下拉刷新,在Flutter
中 ListView
是最经常使用的可滚动组件之一,这里我主要使用ListView
实现列表加载,并配合RefreshIndicator
组件实现下拉刷新;还会使用到ListView的嵌套使用等。html
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, ); } }
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。json
build中body改成包裹一层RefreshIndicator组件,onRefresh
为从新获取数据的方法。segmentfault
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来更新视图。api
@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
中的全局共用方法。app
在ListView builder
中,咱们使用条件判断来让最后一行显示暂无数据、加载中动画(ProgressBar)、数据列表。async
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组件实现列表页面的上拉加载更多下拉刷新。ide
上述的列表是以问卷调查为例,那么这里使用到的ListView
嵌套 ListView
的状况就是问卷的题目列表的展现,你们平时都作过问卷、试卷,知道题目列表包含:大题的题目,小题的题目,小题的选项等,作这种布局,就用到了ListView 嵌套。布局
最重要的是最外层ListView
设置controller: _scrollController
,而且要设置shrinkWrap: true
,根据子widget
的总长度来设置ListView
的长度。而后内里嵌套的全部子ListView
,设置shrinkWrap: true
根据子widget
的总长度来设置ListView
的长度,而且设置 physics: new NeverScrollableScrollPhysics()
禁用问题列表子组件的滚动事件。post
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禁用滚动事件