flutter ListView懒加载(下)——加载完之后的事情怎么处理

上篇文章的原理是讲完了,但一样留下来不少让一个Android开发难堪的问题:
1、下拉刷新卡顿(这个可能会另外开篇讲这个问题,当前先保证业务逻辑是对的)
2、上拉加载一下子会更新好多次数据
3、数据真的拉完的时候接口还在请求
4、数据真的拉完的时候下方那条UI还在转圈
5、空页面时候点击一下就会触发第二页的刷新动作???

问题出来了,这就开始一点点说吧:

1、 先端出状态来!

对于以上问题2,那就该祭出来bool isLoading = false了!
我总得需要让前端UI知道后端正在加载吧,然后给请求方法小小的改造一下:

main_provider.dart节选
 Future<void> getListPage(int pageNum) async {
    isLoading = true;
    await DioUtil.getInstance().get(
      "http://192.168.5.116:20080/contact/listPage",
      {
        'page': pageNum,
        'size': 10,
      },
          () {
        _text = "loading";
        notifyListeners();
      },
          (data) {
        var bean = BaseBean.fromJson(data);
        var pageBean = PageBean.fromJson(bean.data);
        if(pageBean.current==1) {
          _dataList.clear();
        }
        _dataList.addAll(List.from(pageBean.records!.map((e) => ContactBean.fromJson(e))));
        isLoading = false;
        notifyListeners();
      },
          (reason) {
        _text = reason;
        isLoading = false;
        notifyListeners();
      },
    );
  }

大概意思就是说调用该方法时候先把isLoading=true,任何情况下方法执行完成后(包括出错)isLoading=false,这样管他UI刷新洪水滔天,我说在加载就是在加载!
那UI怎么改呢?

main.dart节选
              Expanded(
                child: RefreshIndicator(
                  onRefresh: () async {
                    // 刷新时重置页码并重新加载数据
                    provider.resetPage();
                    await provider.getListPage(provider.pageNum);
                  },
                  child: NotificationListener<ScrollNotification>(
                    onNotification: (scrollInfo){
                      if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) {
                        // 滚动到底部,加载下一页
                        if(!provider.isLoading){
                          provider.addPage();
                          provider.getListPage(provider.pageNum);
                        }
                      }
                      return false;
                    },
                    child: ListView.builder(
                      itemCount:provider.dataList.isEmpty? 0 : provider.dataList.length + 1,
                      itemBuilder: (context, index) {
                        if (index < provider.dataList.length) {
                          return ListTile(
                            title: Text('${provider.dataList[index].lastName} ${provider.dataList[index].firstName}'),
                            subtitle: Text('${provider.dataList[index].phoneNumber}'),
                            onTap: () async  {
                              if(index%2==0){
                                showToast('点击了第${index+1}个联系人:${provider.dataList[index].lastName} ${provider.dataList[index].firstName}',context: context);
                              } else {
                                var data =await showADialog(context, '提示',
                                    '点击了第${index + 1}个联系人:${provider
                                        .dataList[index].lastName} ${provider
                                        .dataList[index].firstName}');
                                showToast(data.toString(), context: context);
                              }
                            },
                          );
                        } else {
                          // 显示加载提示
                          return Center(child: CircularProgressIndicator());
                        }
                      },
                    ),
                  ),
                )
            )

当ListView UI滚动到底部的时候如果provider的isLoading为true的话就会忽略调用下一页的方法。

治完问题2,还有问题3和4,那就再祭出一个状态:bool finalPage = false
这个玩意对UI能通知确实是加载完了,对方法能拦截之后的无效加载。
再改一下provider

main_provider.dart节选
 Future<void> getListPage(int pageNum) async {
    if(finalPage){
      return;
    }
    isLoading = true;
    await DioUtil.getInstance().get(
      "http://192.168.5.116:20080/contact/listPage",
      {
        'page': pageNum,
        'size': 10,
      },
          () {
        _text = "loading";
        notifyListeners();
      },
          (data) {
        var bean = BaseBean.fromJson(data);
        var pageBean = PageBean.fromJson(bean.data);
        if(pageBean.current==1) {
          _dataList.clear();
        }
        _dataList.addAll(List.from(pageBean.records!.map((e) => ContactBean.fromJson(e))));
        isLoading = false;
        if(pageBean.current==pageBean.pages){
          finalPage = true;
        }
        notifyListeners();
      },
          (reason) {
        _text = reason;
        isLoading = false;
        notifyListeners();
      },
    );
  }

当拉到接口的最后一页的时候就把finalPage=true。如果下次还要触发调接口,先看看finalPage,如果finalPage==true了直接return不执行啦!这样就能完美拦截接口这边的调用。
当然UI也得改:

main.dart节选
              Expanded(
                child: RefreshIndicator(
                  onRefresh: () async {
                    // 刷新时重置页码并重新加载数据
                    provider.resetPage();
                    await provider.getListPage(provider.pageNum);
                  },
                  child: NotificationListener<ScrollNotification>(
                    onNotification: (scrollInfo){
                      if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) {
                        // 滚动到底部,加载下一页
                        if(!provider.isLoading || !provider.finalPage){
                          provider.addPage();
                          provider.getListPage(provider.pageNum);
                        }
                      }
                      return false;
                    },
                    child: ListView.builder(
                      itemCount:provider.dataList.isEmpty? 0 : provider.dataList.length + 1,
                      itemBuilder: (context, index) {
                        if (index < provider.dataList.length) {
                          return ListTile(
                            title: Text('${provider.dataList[index].lastName} ${provider.dataList[index].firstName}'),
                            subtitle: Text('${provider.dataList[index].phoneNumber}'),
                            onTap: () async  {
                              if(index%2==0){
                                showToast('点击了第${index+1}个联系人:${provider.dataList[index].lastName} ${provider.dataList[index].firstName}',context: context);
                              } else {
                                var data =await showADialog(context, '提示',
                                    '点击了第${index + 1}个联系人:${provider
                                        .dataList[index].lastName} ${provider
                                        .dataList[index].firstName}');
                                showToast(data.toString(), context: context);
                              }
                            },
                          );
                        } else {
                          // 显示加载提示
                          return Center(child: provider.finalPage
                              ? Text('没有更多数据了')
                              : CircularProgressIndicator()
                          );
                        }
                      },
                    ),
                  ),
                )
            )

在检查是否正在加载那再加一个条件,达到“如果没有加载或不是最后一页再去刷新”的逻辑效果
下面的加载提示也可以借这个条件可以变换一下,加载完最后一行就可以提示没有更多数据了。

注:记得在provider中刷新的时候将状态归位哈!

2、问题5的发现和解决方法

正当我以为大功告成的时候,我貌似碰了一下屏幕就触发刷新数据了——但很明显不是第一页的!
数据库查看、接口日志全部指向第二页!
虽说provider中_pageNum默认是1,但这回触发的不是下拉刷新是上拉加载,我第一页数据还没薅回来呢直接薅第二页?
那_pageNum默认给0吧,反正刷新后会给1,如果触发加载的话0+1=1嘛
收工!


至于问题1,可能需要单开一篇看这个事情

上一篇
下一篇