Flutter 經常使用ListView和RefreshIndicator組件實現列表頁面的上拉加載更多下拉刷新

APP中最多見的就是列表頁面,上拉加載更多,下拉刷新,在FlutterListView 是最經常使用的可滾動組件之一,這裏我主要使用ListView實現列表加載,並配合RefreshIndicator組件實現下拉刷新;還會使用到ListView的嵌套使用等。html

建立 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。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();
  }

以上使用到的showToastFlutter 經常使用的提示框showToast、showLoading、showConfirmDialog寫在lib/common/toast.dart中的全局共用方法。app

ListView 顯示

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的狀況就是問卷的題目列表的展現,你們平時都作過問卷、試卷,知道題目列表包含:大題的題目,小題的題目,小題的選項等,作這種佈局,就用到了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禁用滾動事件

相關文章
相關標籤/搜索