美文网首页我爱编程一起来学Flutter~Android开发经验谈
Flutter 21: 图解 ListView 下拉刷新与上拉加

Flutter 21: 图解 ListView 下拉刷新与上拉加

作者: 阿策神奇 | 来源:发表于2018-11-12 22:58 被阅读63次

          小菜前段时间整理了两种 ListView 的异步加载数据时,下拉刷新与上滑加载更多的方式,每种方式都有自己的优势,网上也有很多大神讲解过 ListView 数据流的种种处理方式,小菜根据实际遇到的情况整理一下尝试的第三种方案。

    RefreshIndicator 下拉刷新

          Flutter 提供了自带刷新效果的 RefreshIndicator,这也是网上大神们用的最多的 Widget 之一,使用方式也很简单,RefreshIndicator 中提供了一个刷新的回调入口 onRefresh,仅需在该回调接口中处理数据请求即可,如下:

    // 刷新时数据请求
    Future<Null> _loadRefresh() async {
      await Future.delayed(Duration(seconds: 2), () {
        setState(() {
          dataItems.clear();
          lastFileID = '0';
          rowNumber = 0;
          _getNewsData(lastFileID, rowNumber);
          return null;
        });
      });
    }
    
    // 请求接口整合数据
    _getNewsData(var lastID, var rowNum) async {
      await http
          .get(
          'https://XXX.../getArticles?...&lastFileID=${lastID}&rowNumber=${rowNum}')
          .then((response) {
        if (response.statusCode == 200) {
          var jsonRes = json.decode(response.body);
          newsListBean = NewsListBean(jsonRes);
          if (lastID == '0' && rowNum == 0 && dataItems != null) {
            dataItems.clear();
          }
          setState(() {
            if (newsListBean != null &&
                newsListBean.list != null &&
                newsListBean.list.length > 0) {
              for (int i = 0; i < newsListBean.list.length; i++) {
                dataItems.add(newsListBean.list[i]);
              }
              lastFileID = newsListBean.list[newsListBean.list.length - 1].fileID.toString();
              rowNumber += newsListBean.list.length;
            } else {}
          });
        }
      });
    }
    
    // 绑定列表数据
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text("第三种加载方式"),
        ),
        body: new RefreshIndicator(
          child: ListView.builder(
            itemCount: items.length,
            itemBuilder: buildListData(context, dataItems[index])
          ),
          onRefresh: _loadRefresh,  // 刷新回调
        ));
    }
    

    ScrollController 上滑动加载更多

          至此,列表的下拉刷新就完成了,接下来处理【上滑加载更多】,这时我们可以借助 ScrollController,用来监听列表是否滑动到底部,主要分两步:

    1. 初始化时添加监听事件,判断是否滑动到最底部;
    2. ListView 中添加监听方法。
    ScrollController _scrollController = new ScrollController();
    
    @override
    void initState() {
      super.initState();
      _scrollController.addListener(() {
        if (_scrollController.position.pixels ==
            _scrollController.position.maxScrollExtent) {
          _getMoreData();  // 当滑到最底部时调用
        }
      });
      _getMoreData();  // 数据初始化
    }
    
    @override
    Widget build(BuildContext context) {
      return Scaffold(
          appBar: AppBar(
            title: Text("第三种加载方式"),
          ),
          body: ListView.builder(
            itemCount: items.length,
            itemBuilder: buildListData(context, dataItems[index]),
            controller: _scrollController,
          ));
    }
    

          至此,列表的下拉刷新与上滑加载更多就基本完成了;接下来需要将两种合并使用,也很简单,如下:

    body: new Padding(
      padding: EdgeInsets.all(2.0),
      child: RefreshIndicator(
          onRefresh: _loadRefresh,
          child: ListView.builder(
            itemCount: dataItems.length,
            physics: const AlwaysScrollableScrollPhysics(),
            itemBuilder: (context, index) {
               return buildListData(context, dataItems[index]);
            },
            controller: _scrollController,
          )));
    

          Tips: 注意处理好数据接口请求内容。

    小优化

    优化一:【上滑加载更多】添加动画效果
    1. 添加一个加载更多的布局 Widget
    2. itemCount 中将 item 个数 +1
    3. 添加监听判断,当滑到最后一个 item 时展示加载更多到布局 Widget
    body: new Padding(
      padding: EdgeInsets.all(2.0),
      child: RefreshIndicator(
          onRefresh: _loadRefresh,
          child: ListView.builder(
            itemCount: dataItems.length + 1,
            physics: const AlwaysScrollableScrollPhysics(),
            itemBuilder: (context, index) {
              if (index == dataItems.length) {
                return _buildProgressIndicator();
              } else {
                return buildListData(context, dataItems[index]);
              }
            },
            controller: _scrollController,
          )));
    
    // 加载更多 Widget
    Widget _buildProgressIndicator() {
      return new Padding(
          padding: EdgeInsets.fromLTRB(0.0, 14.0, 0.0, 14.0),
          child: new Opacity(
              opacity: isShowLoading ? 1.0 : 0.0,
              child: new Row(
                mainAxisAlignment: MainAxisAlignment.center,
                mainAxisSize: MainAxisSize.max,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  new SpinKitChasingDots(color: Colors.blueAccent, size: 26.0),
                  new Padding(
                      padding: EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 0.0),
                      child: new Text('正在加载中...'))
                ],
              )));
    }
    
    优化二:第一次初始化加载数据时添加 loading 动画

          RefreshIndicator 中自带刷新的动画,所以小菜只是在第一次加载数据时添加一个 loading 动画,小菜只是填了一个小小的状态判断,如下包括异常情况下的失败页。

    Widget childWidget() {
      Widget childWidget;
      if (newsListBean != null &&
          (newsListBean.success != null && !newsListBean.success)) {
        isFirstLoading = false;
        childWidget = new Stack(children: <Widget>[
          new Padding(
              padding: new EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 100.0),
              child: new Center(
                  child: Image.asset( 'images/icon_wrong.jpg', width: 120.0, height: 120.0, ))),
          new Padding(
              padding: new EdgeInsets.fromLTRB(0.0, 100.0, 0.0, 0.0),
              child: new Center(
                  child: new Text(
                '抱歉!暂无内容哦~',
                style: new TextStyle(fontSize: 18.0, color: Colors.blue),
              )))
        ]);
      } else if (dataItems != null && dataItems.length != 0) {
        isFirstLoading = false;
        childWidget = new Padding(
            padding: EdgeInsets.all(2.0),
            child: RefreshIndicator(
                onRefresh: _loadRefresh,
                child: ListView.builder(
                  itemCount: dataItems.length + 1,
                  physics: const AlwaysScrollableScrollPhysics(),
                  itemBuilder: (context, index) {
                    if (index == dataItems.length) {
                      return _buildProgressIndicator();
                    } else {
                      return buildListData(context, dataItems[index]);
                    }
                  },
                  controller: _scrollController,
                )));
      } else {
        if (isFirstLoading) {  // 只有在第一次加载数据时才会展示自定义 loading
          childWidget = new Center(
            child: new Card(
                child: new Stack(children: <Widget>[
              new Padding(
                  padding: new EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 35.0),
                  child: new Center(
                      child: SpinKitFadingCircle( color: Colors.blueAccent, size: 30.0, ))),
              new Padding(
                  padding: new EdgeInsets.fromLTRB(0.0, 35.0, 0.0, 0.0),
                  child: new Center(
                    child: new Text('正在加载中,莫着急哦~'),
                  ))
            ])),
          );
        } else {}
      }
      return childWidget;
    }
    
    优化三:借助 Future.delayed() 进行延迟加载,使数据请求衔接性更好。
    _getMoreData() async {
      if (!isShowLoading) {
        setState(() {
          isShowLoading = true;
        });
        await Future.delayed(Duration(seconds: 2), () {
          setState(() {
            _getNewsData(lastFileID, rowNumber);
            isShowLoading = false;
            return null;
          });
        });
      }
    }
    

          小菜刚接触 Flutter 时间不长,还有很多不清楚和不理解的地方,如果有不对的地方还希望多多指教。以下是小菜公众号,欢迎闲来吐槽〜

    公众号.jpg

    相关文章

      网友评论

        本文标题:Flutter 21: 图解 ListView 下拉刷新与上拉加

        本文链接:https://www.haomeiwen.com/subject/lzphfqtx.html