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禁用滾動事件