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

Posted iblade

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

以上是关于RecyclerView ViewHolder getAdapterPosition()返回-1(NO_POSITION)的主要内容,如果未能解决你的问题,请参考以下文章

Recyclerview ViewHolder中的TextView - OnClickListener

RecyclerView.ViewHolder 是不是总是必须是内部类?

在“ListAdapter”或“RecyclerView.ViewHolder”做繁重的工作?

等效于 RecyclerView 中 ViewHolder 的 onAttachedToWindow()/onDetachedFromWindow()

ViewHolder 中的嵌套 RecyclerView 破坏了折叠工具栏布局

不可见的 ViewHolder 动画在 RecyclerView 中延迟