在使用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