在使用RecyclerView進(jìn)行數(shù)據(jù)移除或者增加的時候,有時候會出現(xiàn)以下這個異常:
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)
那么,這個異常是如何產(chǎn)生的,怎么解決呢?
在RecyclerView中废岂,有四種方式刷新數(shù)據(jù):
1.notifyDataSetChanged();這個是ListView的使用方法漂辐,在RecyclerView中同樣適用泪喊,但是這個不是官方推薦的,因?yàn)镽ecyclerView相對于ListView提供了局部刷新接口髓涯,而且局部刷新有動畫效果袒啼。
2.notifyItemRangeRemoved();
3.notifyItemRangeInserted()纬纪;
4.notifyItemRangeChanged()蚓再;
刷新數(shù)據(jù)的后三種方法推薦使用,但是使用不好會出現(xiàn)剛才文章開始的異常包各;下面分析這個異常產(chǎn)生的原因摘仅。
下面看看以前個人開發(fā)是遇到的一個此類問題:
/**
* 插入新數(shù)據(jù)
*/
public void insertData(List<T> 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());
}
以上代碼中,當(dāng)insertData中有空數(shù)據(jù)的時候就會出現(xiàn)異常问畅,如果有空數(shù)據(jù)娃属,后面notifyItemRangeInserted中的起始位置和增加的item數(shù)量就和mDatas中的不一致,mDatas我們成為adapter內(nèi)部數(shù)據(jù)护姆,insertData成為外部數(shù)據(jù)矾端,我們應(yīng)該實(shí)時保持?jǐn)?shù)據(jù)的一致性。
修改后的代碼為:
/**
* 插入新數(shù)據(jù)
*/
public void insertData(List<T> 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數(shù)據(jù)的刷新操作要分解為“原子”操作秩铆,“原子”操作就三個,移除灯变,增加殴玛,修改。舉個例子說明一下:
public void notifyData(List<PoiItem> poiItemList) {
if (poiItemList != null ) {
mPoiItems.clear();
mPoiItems.addAll(poiItemList);
notifyItemRangeChanged(0, poiItemList.size());
}
}
在上面的操作中添祸,首先是移除了數(shù)據(jù)滚粟,但是沒有刷新RecyclerView,然后又增加了數(shù)據(jù)刃泌,這時候刷新了RecyclerView坦刀,這個時候就會出問題愧沟,所有把這個過程拆為兩步:
public void notifyData(List<PoiItem> poiItemList) {
if (poiItemList != null) {
int previousSize = mPoiItems.size();
mPoiItems.clear();
notifyItemRangeRemoved(0, previousSize);
mPoiItems.addAll(poiItemList);
notifyItemRangeInserted(0, poiItemList.size());
}
}
所以總結(jié)一下為:如果使用RecyclerView的局部刷新功能蔬咬,每次adapter的內(nèi)部數(shù)據(jù)集發(fā)生改變時鲤遥,都要主動調(diào)用一下數(shù)據(jù)刷新,以保持?jǐn)?shù)據(jù)的一致性林艘。