Nealyang/PersonalBlog

下拉刷新 & 加载更多

Nealyang opened this issue · 1 comments

前言

目前,我们的app首页已经有个样子出来了,并且数据也不是开玩笑的,都是真实数据,对于首页而言,我们当然还需要添加翻页的功能,甚至包括下拉刷新的功能。幸运是的,Flutter对于这方面来说,给我们封装的还是相当的便利的。

修复上面请求接口bug

今天重新打开页面的时候发现了页面报错,debug了一下发现是接口数据过来有些字段为空导致的。遂这里请求接口以及数据的处理上,做了一些调整

  • lib/util/data_utils.dart
      try {
        IndexCell cellData = new IndexCell.fromJson(responseList[i]);
        resultList.add(cellData);
      } catch (e) {
        // No specified type, handles all
        print('Something really unknown: $i');
      }

getIndexListData 方法里面加了一层异常捕获,毕竟不是我们自己跟后端的约束,对业务也并非清楚,所以里面一些字段我们也不清楚,这些问题也是难免。

  • lib/model/indexCell.dart
  factory IndexCell.fromJson(Map<String, dynamic> json) {
    String _tag = '';
    if(json['tags'].length>0){
      _tag = '${json['tags'][0]['title']}/';
    }
    return IndexCell(
      hot: json['hot'],
      collectionCount: json['collectionCount'],
      commentCount: json['commentsCount'],
      tag: '$_tag${json['category']['name']}',
      username: json['user']['username'],
      createdTime: Util.getTimeDuration(json['createdAt']),
      title: json['title'],
      detailUrl: json['originalUrl'],
      isCollection: json['type'] ,
    );
  }

添加了对tag的为空判断

下拉刷新

下拉刷新其实就是在我们之前调用下我们的getList方法。难点可能就是如何触发这个下拉刷新呢?非常幸运,Material 中 提供了 RefreshIndicator widget

同样,我们可以从他的源码中看到他的属性

img

  • lib/index_page.dart
  // 下拉刷新
  Future<void> _onRefresh() async{//The RefreshIndicator onRefresh callback must return a Future.
    _listData.clear();
    setState(() {
      _listData = _listData;
      //注意这里需要重置一切请求条件
      _hasMore = true;
    });
    getList(false);
    return null;
  }
  //build 方法返回如下
    return RefreshIndicator(
      onRefresh: _onRefresh,
      child: ListView.builder(
        itemCount: _listData.length + 2, //添加一个header 和 loadMore
        itemBuilder: (context, index) => _renderList(context, index),
      ),
    );

下拉刷新的方法中setState是为了重新build,毕竟空列表页面会有loading的出现。

loadMore的实现

  • lib/index_page.dart
    ListView.builder中有一个属性为Controller,他可以监听页面的滚动,所以我们在ListView.builder中加入这个属性
  ScrollController _scrollController = new ScrollController();
  

  // build方法中
    return RefreshIndicator(
      onRefresh: _onRefresh,
      child: ListView.builder(
        itemCount: _listData.length + 2, //添加一个header 和 loadMore
        itemBuilder: (context, index) => _renderList(context, index),
         controller: _scrollController,
      ),
    );

在页面初始化的时候键入滚动监听,并且触发getList方法

  @override
    void initState() {
      super.initState();
      getList(false);
      _scrollController.addListener(() {
        if (_scrollController.position.pixels ==
            _scrollController.position.maxScrollExtent) {
              print('loadMore');
          getList(true);
        }
      });
    }

一些别的变量的作用,由于触底这个动作可以反复触发,而网络请求是异步的,所以我们不可能在每一次触底都要去发送过一次请求,而是再一次请求结束后,再次触底才会再次发送请求。所以这里我们加入了 _isRequesting 的flag

  bool _isRequesting = false; //是否正在请求数据的flag
  bool _hasMore = true;

请求的方法也做了稍微的跳转

  getList(bool isLoadMore) {
    if (_isRequesting || !_hasMore) return;
    if (!isLoadMore) {
      // reload的时候重置page
      _pageIndex = 0;
    }
    _params['before'] = pageIndexArray[_pageIndex];
    _isRequesting = true;
    DataUtils.getIndexListData(_params).then((result) {
      _pageIndex += 1;
      List<IndexCell> resultList = new List();
      if(isLoadMore){
        resultList.addAll(_listData);
      }
      resultList.addAll(result);
      setState(() {
        _listData = resultList;
        _hasMore = _pageIndex < pageIndexArray.length;
        _isRequesting = false;
      });
    });
  }

添加加载器

页面触底,发送请求,讲道理应该是要给用户一个反馈的,由于这个可以很多页面公用,所以当然,我们将其封装为一个widget

  • lib/widgets/load_more.dart
    import 'package:flutter/material.dart';
    
    class LoadMore extends StatelessWidget {
      final bool hasMore;
    
      LoadMore(this.hasMore);
    
      @override
      Widget build(BuildContext context) {
        if (hasMore) {
          return Container(
            height: 70.0,
            child: Center(
              child: Opacity(
                opacity: 1.0,
                child: CircularProgressIndicator(
                  strokeWidth: 3.0,
                ),
              ),
            ),
          );
        }
        return Container(
          height: 70.0,
          child: Center(
            child: Text('亲,我也是有底线的',
                style: TextStyle(color: Theme.of(context).accentColor)),
          ),
        );
      }
    }

最终,我们的页面效果就出来了。 代码地址

loadMore

总结

通过这一章节,你应该学会

  • Dart中异常的处理和捕获
  • 下拉刷新
  • 加载更多
Eoous commented

将CircularProgressIndicator传给ListView.builder的时候为什么它转了一会儿,单独测试的时候CircularProgressIndicator一直在转。。。