RecyclerView ViewHolder getAdapterPosition()返回-1(NO_POSITION)

RecyclerView 自帶刪除Item方法notifyItemRemoved(position);頻繁點擊刪除時,突然拋出越界異常顯示 position = -1 ;

java.lang.ArrayIndexOutOfBoundsException: length=10; index=-1
at java.util.ArrayList.get(ArrayList.java:310)

WTF???活見鬼!

查看getAdapterPosition()源碼:

        public final int getAdapterPosition() {
            return this.mOwnerRecyclerView == null ? -1 : this.mOwnerRecyclerView.getAdapterPositionFor(this);
        }


    int getAdapterPositionFor(RecyclerView.ViewHolder viewHolder) {
        return !viewHolder.hasAnyOfTheFlags(524) && viewHolder.isBound() ? this.mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition) : -1;
    }

從getAdapterPositionFor(ViewHolder viewHolder)看不出什麼信息,切換低版本源碼查看:

int getAdapterPositionFor(ViewHolder viewHolder) {
    if (viewHolder.hasAnyOfTheFlags( ViewHolder.FLAG_INVALID |
            ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)
            || !viewHolder.isBound()) {
        return RecyclerView.NO_POSITION;
    }
    return mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition);
}

當ViewHolder處於FLAG_REMOVED 等狀態時會返回NO_POSITION。
我們看一下官方文檔解釋:

Returns the Adapter position of the item represented by this ViewHolder.
Note that this might be different than the getLayoutPosition() if there are pending adapter updates but a new layout pass has not happened yet.
RecyclerView does not handle any adapter updates until the next layout traversal. This may create temporary inconsistencies between what user sees on the screen and what adapter contents have. This inconsistency is not important since it will be less than 16ms but it might be a problem if you want to use ViewHolder position to access the adapter. Sometimes, you may need to get the exact adapter position to do some actions in response to user events. In that case, you should use this method which will calculate the Adapter position of the ViewHolder.
Note that if you’ve called notifyDataSetChanged(), until the next layout pass, the return value of this method will be NO_POSITION.
The adapter position of the item if it still exists in the adapter. NO_POSITION if item has been removed from the adapter, notifyDataSetChanged() has been called after the last layout pass or the ViewHolder has already been recycled.

在這裏插入圖片描述
其中Note that if you’ve called notifyDataSetChanged(), until the next layout pass, the return value of this method will be NO_POSITION。解釋了返回-1的原因,大意是:notify未完成時,調用getAdapterPosition就會返回NO_POSITION(-1)。

我看網上有人說採用getLayouPosition()避免-1,然並卵。
二者何異?
在這裏插入圖片描述

When adapter contents change (and you call notify*) RecyclerView requests a new layout. From that moment, until layout system decides to calculate a new layout (<16 ms), the layout position and adapter position may not match because layout has not reflected adapter changes yet.

adapter和layout的位置會有時間差(<16ms), 如果你改變了Adapter的數據然後刷新視圖, layout需要過一段時間纔會更新視圖, 在這段時間裏面, 這兩個方法返回的position會不一樣。

另外stackoverflow上有答案還提到, 在notifyDataSetChanged之後並不能馬上獲取Adapter中的position, 要等佈局結束之後才能獲取到.而對於Layout的position, 在notifyItemInserted之後, Layout不能馬上獲取到新的position, 因爲佈局還沒更新(需要<16ms的時間刷新視圖), 所以只能獲取到舊的, 但是Adapter中的position就可以馬上獲取到最新的position。

但是這都不能解決返回-1突發的crash,我們怎麼做?

  • 方法一:在ViewHolder中持有數據源Bean。 通過bean在list中的位置來獲取position
  • 方法二:在使用position地方判斷 if(position<0) return;

官方文檔 原文地址:https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ViewHolder#getadapterposition

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章