关于RecyclerView数据刷新的问题

在使用RecyclerView进行数据移除或者增加的时候,有时候会出现以下这个异常:

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{431a7450 position=1 id=-1, oldPos=-1, pLpos:-1 scrap [attachedScrap] tmpDetached no parent}  
    at android.support.v7.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:4251)  
    at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4382)  
    at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4363)  
    at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1961)  
    at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1370)  
    at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1333)  
    at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:562)  
    at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:2900)  
    at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:3071)  

那么,这个异常是如何产生的,怎么解决呢?

在RecyclerView中,有四种方式刷新数据:
1.notifyDataSetChanged();这个是ListView的使用方法,在RecyclerView中同样适用,但是这个不是官方推荐的,因为RecyclerView相对于ListView提供了局部刷新接口,而且局部刷新有动画效果。
2.notifyItemRangeRemoved();
3.notifyItemRangeInserted();
4.notifyItemRangeChanged();

刷新数据的后三种方法推荐使用,但是使用不好会出现刚才文章开始的异常;下面分析这个异常产生的原因。

下面看看以前个人开发是遇到的一个此类问题:

/**
     * 插入新数据
     */
    public void insertData(List insertedData) {
        if (insertedData == null) {
            Log.e(TAG, "insertData(list) list is null");
            return;
        }
        for (T data : insertedData){
            if (data!=null){
                mDatas.add(data);
            }
        }
        notifyItemRangeInserted(mDatas.size() - insertedData.size(), insertedData.size());
    }

以上代码中,当insertData中有空数据的时候就会出现异常,如果有空数据,后面notifyItemRangeInserted中的起始位置和增加的item数量就和mDatas中的不一致,mDatas我们成为adapter内部数据,insertData成为外部数据,我们应该实时保持数据的一致性。

修改后的代码为:
/**
     * 插入新数据
     */
    public void insertData(List insertedData) {
        if (insertedData == null) {
            Log.e(TAG, "insertData(list) list is null");
            return;
        }
        int index = 0;
        for (T data : insertedData) {
            if (data != null) {
                mDatas.add(data);
                index++;
            }
        }
        notifyItemRangeInserted(mDatas.size() - index, index);
    }

还有一个问题,对RecyclerView数据的刷新操作要分解为“原子”操作,“原子”操作就三个,移除,增加,修改。举个例子说明一下:

public void notifyData(List poiItemList) {
    if (poiItemList != null ) {
        mPoiItems.clear();
        mPoiItems.addAll(poiItemList);
        notifyItemRangeChanged(0, poiItemList.size());
    }
}

在上面的操作中,首先是移除了数据,但是没有刷新RecyclerView,然后又增加了数据,这时候刷新了RecyclerView,这个时候就会出问题,所有把这个过程拆为两步:

public void notifyData(List poiItemList) {
    if (poiItemList != null) {
        int previousSize = mPoiItems.size();
        mPoiItems.clear();
        notifyItemRangeRemoved(0, previousSize);
        mPoiItems.addAll(poiItemList);
        notifyItemRangeInserted(0, poiItemList.size());
    }
}

所以总结一下为:如果使用RecyclerView的局部刷新功能,每次adapter的内部数据集发生改变时,都要主动调用一下数据刷新,以保持数据的一致性。

参考文章:
http://www.jianshu.com/p/2eca433869e9

你可能感兴趣的:(关于RecyclerView数据刷新的问题)