RecyclerView下拉刷新时快速滑动崩溃的问题解决

图1

前提

这个问题是在做项目的时候发现的,当然并非我第一个发现,而是看到了一些网上的信息后,发现很多都没有说清楚到底是什么导致的此问题,而只是有一些解决方法,也就是知其所以然,而不知其所以然。觉得有必要留下一个TAG,以便后面忘记了,还可以找到解决方法。当然,如果知道问题是什么了,由什么引起的后,也应该会知道怎么解决。

场景

在使用RecyclerView时,我们都会搭配SwipeRefreshLayout来做下拉刷新,而问题点就在这个下拉刷新的地方,我们下拉刷新一般会先清空适配器中的数据,然后再去请求数据进行填充,问题就发生在这个当我们清空数据后,这时我们进行了快速滑动,此时会抛出异常,如下所示:
图1由图上可以看出这个问题是RecyclerView内部抛出来的,也就是说这可能是RecyclerView的内部bug导致的,不管那么多了,现在问题我们已经清楚了,就是由于下拉刷新,清空数据后,此时页面刚好在滑动,此时就会抛出异常。

解决方法

问题点清楚以后,我们来分析其解决方法,这里应该有两个方面来进行分析,第一个方面就是下拉刷新时是否可以将数据不要清空;另一个方面是下拉刷新时当数据没有加载完成时,不要进行滑动。思路有了之后,我们就从这两个方面来分别考虑其可实现的可能。

1.下拉刷新时不清空数据

我个人想到的是这种方法,在我对RecyclerView.Adapter进行封装的RecyclerViewBaseAdapter里就是采用此方法来处理数据的。

public void addData(List<D> items) {
    boolean isChangeData = items.removeAll(mData);

    if (items.size() == 0) {
        //走到这里,说明加载的数据全部为重复数据,所以数据没有变化
        if (mProgressBarView != null) {
            mProgressBarView.setVisibility(View.GONE);
            mNotMoreDataView.setVisibility(View.GONE);
        }
    }

    mData.addAll(items);

    notifyDataSetChanged();

这里就是将每次添加到适配器中的数据先使用removeAll()去除重复项,然后再添加到数据集合中,当然,这里我们数据集合里往往会存放对象,而对象之间比较是否相等时,我们需要将此对象实现hashCode()equeal()两个方法。
在这里处理下拉刷新时,还需要注意这样处理,就是当是下拉刷新时,需要将数据添加到数据集合的前面,而不是尾部。这里我们需要对下拉刷新做一个判断:

 @Override
public void setData(List list) {
    errorString = null;
    list.removeAll(datas);

    if (isDownRefresh) {
        //是否是下拉刷新
        datas.addAll(0, list);
    } else {
        datas.addAll(list);
    }
    notifyDataSetChanged();
}

当是下拉刷新时,我们将请求的数据添加到position为0的位置。这样就大概可以解决我们上面抛异常的问题了,当然这里如果下拉刷新时,新数据超过一页的时候,数据的显示,也就是第二页数据显示的位置,可能还需要在这个setData()方法里根据list.reomve(datas)返回的是否是boolean值还再进行插入数据集合的处理了。

2.当下拉刷新时,静止滑动

这个方法呢,体验就没那么好了,如果数据没加载完成时,不让滑动?数据请求比较快,还好一点,如果比较慢的话,会给用户造成app已死的假象,因为不能动了嘛。当然,如果没有什么好的解决方法时,也只能这么勉强先应付着吧。

mRecyclerView.setOnTouchListener(
    new View.OnTouchListener() {
      @Override
      public boolean onTouch(View v, MotionEvent event) {
        if (mIsRefreshing) {
          return true;
        } else {
          return false;
        }
      }
    }
);

当刷新时设将mIsRefreshing设置为true,拦截滑动事件,而当刷新完毕后,再将mIsRefreshing设置为false.
好了,使用上面两种方法都是可以解决抛异常的,但我个人还是倾向于使用第一种方法。