RecyclerView检测到不一致问题,如何在滚动时更改RecyclerView的内容

64

我正在使用RecyclerView来显示项目名称。我的行只包含单个TextView。项目名称存储在List<String> mItemList中。

要更改RecyclerView的内容,我替换mItemList中的字符串,并在RecyclerViewAdapter上调用notifyDataSetChanged()。

但是,如果我在RecyclerView滚动时尝试更改mItemList的内容,有时会出现java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 157(offset:157).state:588错误。

如果mItemList的大小小于之前的大小,则会发生这种情况。那么更改RecyclerView的内容的正确方法是什么?这是RecyclerView中的错误吗?

以下是异常的完整堆栈跟踪:

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 157(offset:157).state:588
        at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3300)
        at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:3258)
        at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1803)
        at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1302)
        at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1265)
        at android.support.v7.widget.LinearLayoutManager.scrollBy(LinearLayoutManager.java:1093)
        at android.support.v7.widget.LinearLayoutManager.scrollVerticallyBy(LinearLayoutManager.java:956)
        at android.support.v7.widget.RecyclerView$ViewFlinger.run(RecyclerView.java:2715)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:725)
        at android.view.Choreographer.doCallbacks(Choreographer.java:555)
        at android.view.Choreographer.doFrame(Choreographer.java:524)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:711)
        at android.os.Handler.handleCallback(Handler.java:615)
        at android.os.Handler.dispatchMessage(Handler.java:92)
        at android.os.Looper.loop(Looper.java:137)
        at android.app.ActivityThread.main(ActivityThread.java:4921)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:511)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1027)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:794)
        at dalvik.system.NativeStart.main(Native Method)

适配器视图代码:

private static class FileListAdapter extends RecyclerView.Adapter<FileHolder> {
    private final Context mContext;
    private final SparseBooleanArray mSelectedArray;
    private final List<String> mList;

    FileListAdapter(Context context, List<String> list, SparseBooleanArray selectedArray) {
        mList = list;
        mContext = context;
        mSelectedArray = selectedArray;
    }


    @Override
    public FileHolder onCreateViewHolder(ViewGroup viewGroup, int i) {

        View view = LayoutInflater.from(viewGroup.getContext()).inflate(
                R.layout.file_list_item, viewGroup, false);

        TextView tv = (TextView) view
                .findViewById(R.id.file_name_text);
        Typeface font = Typeface.createFromAsset(viewGroup.getContext().getAssets(),
                viewGroup.getContext().getString(R.string.roboto_regular));
        tv.setTypeface(font);

        return new FileHolder(view, tv);
    }

    @Override
    public void onBindViewHolder(FileHolder fileHolder, final int i) {

        String name = mList.get(i);

        // highlight view if selected
        setSelected(fileHolder.itemView, mSelectedArray.get(i));

        // Set text
        fileHolder.mTextView.setText(name);
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }
}

private static class FileHolder extends RecyclerView.ViewHolder {

    public final TextView mTextView;

    public FileHolder(View itemView, TextView tv) {
        super(itemView);
        mTextView = tv;
    }
}

请发布您的适配器代码以及如何使用它。 - Blaze Tama
我在这个帖子中发布了我的解决方案。 - César Cobo
26个回答

43

编辑: 此问题现已修复,如果你仍然遇到同样的异常,请确保你只从主线程更新适配器的数据源,并在此之后调用适当的适配器通知方法。

旧回答: 这似乎是 RecyclerView 中的一个 bug,已在这里这里报告。希望它会在下一个发布版本中得到修复。


3
仍然面临问题... :( - theapache64
我怀疑这与在ScrollView中放置RecyclerView有关。我也遇到过这种情况,但不确定。 尝试更改布局,看看是否有所帮助? - revolutionary
19
我正在使用 compile 'com.android.support:recyclerview-v7:23.0.0',但仍然遇到相同的问题。 - user1682446
2
如果您尝试从后台线程更改列表内容,则会遇到相同的问题。 - jimmy0251
1
@jimmy0251 还未解决:D 我已经找到了一个解决方法,但是请问为什么我们不能从另一个线程/活动/片段更改列表,然后适配器可以全面处理它,我们只需要调用notifyDataSet... 唉..... - mfaisalhyder
显示剩余3条评论

17

对我来说没问题。使用NotifyDataSetChanged();

public class MyFragment extends Fragment{

    private MyAdapter adapter;

    // Your code

    public void addArticle(){
        ArrayList<Article> list = new ArrayList<Article>();
        //Add one article in this list

        adapter.addArticleFirst(list); // or adapter.addArticleLast(list);
    }
}

public class ArticleAdapterRecycler extends RecyclerView.Adapter<ArticleAdapterRecycler.ViewHolder> {

    private ArrayList<Article> Articles = new ArrayList<Article>();
    private Context context;


    // Some functions from RecyclerView.Adapter<ArticleAdapterRecycler.ViewHolder>    

    // Add at the top of the list.

    public void addArticleFirst(ArrayList<Article> list) {
        Articles.addAll(0, list);
        notifyDataSetChanged();
    }

    // Add at the end of the list.

    public void addArticleLast(ArrayList<Article> list) {
        Articles.addAll(Articles.size(), list);
        notifyDataSetChanged();
    }
}

2
在使用notifyDataSetChanged()时需要注意的一点是,默认情况下它不会对修改的项目进行动画处理,而notifyItemChanged()会。 - Krøllebølle
1
当我尝试在适配器中插入项目并在空适配器上使用notifyItemRangeInserted(0, newNotifications.size());时,我遇到了相同的错误。我通过检查适配器是否为空来解决了这个问题,然后使用notifydatasetchanged,否则使用notifyItemRangeInserted(0, newNotifications.size()); - Rafael
7
如果您在适配器上调用setHasStableIds(true)并覆盖getItemId方法以为每一行返回唯一的长整型值,则notifyDataSetChanged将对项目进行动画处理。一旦完成这两个步骤,它将触发动画效果。 - PirateApp

10

6
mIsRefreshing是什么? - user1682446
@satnamsingh 似乎是一个布尔标志,用于指示刷新已经开始但尚未完成。 - ataulm
链接已失效。我在哪里可以设置 mIsRefreshing = true - Bhavin Patel

8
问题绝对不是由于recyclerview滚动引起的,而是与notifyDataSetChanged()有关。我有一个recycler view,在其中不断更改数据,即添加和删除数据。每当我添加项目到列表中时,我都会调用notifyDataSetChanged(),但在删除项目或清空列表时,我没有刷新适配器。
因此,为了解决这个问题:
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 2(offset:2).state:12 at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5456)

我在需要的地方调用了 adapter.notifyDataSetChanged() ,并且在清空列表后执行。

if (!myList.isEmpty()) {
        myList.clear();
        myListAdapter.notifyDataSetChanged();
    }

自那时以来,我再也没有遇到过异常。希望对其他人也能有同样的效果。 :)

8
尽管已经在被接受的答案中提供了一些有用的关于此问题的超链接,但是RecyclerView在滚动时的这种行为并不是一个bug
如果您看到此异常,很可能是因为在RecyclerView的内容“改变”后,您忘记通知适配器。人们只在向数据集添加项目后调用notifyDataSetChanged()。然而,不一致性不仅发生在重新填充适配器后,还会在删除项目或清除数据集时发生。您应该通过通知适配器来刷新视图以反映此更改:
public void refillAdapter(Item item) {

    adapter.add(item);
    notifyDataSetChanged();

}

public void cleanUpAdapter() {

    adapter.clear();
    notifyDataSetChanged(); /* Important */

}

在我的情况下,我尝试在onStop()中清除适配器,在onStart()中重新填充。但是,在使用clear()清除适配器后,我忘记调用notifyDataSetChanged()。因此,每当我从onStop()更改状态为onStart()并在数据集重新加载时迅速滚动RecyclerView时,就会看到此异常。如果我等待重新加载完成而不滚动,则这次可以顺利恢复适配器,就不会出现异常。
简而言之,当RecyclerView处于视图更改状态时,它是不一致的。如果您尝试在处理数据集中的更改时滚动视图,则会看到java.lang.IndexOutOfBoundsException:Inconsistency detected。为了消除这个问题,您应该在数据集更改后立即通知适配器。

7
如果您使用了 RecyclerView,那么这个问题就会出现。
adapter.setHasStableIds(true);

如果您设置了这样的值,请移除它,并在适配器内更新数据集;
如果仍然遇到问题,请在获取新数据后使所有视图无效,然后再更新数据集。


4

我遇到了同样的问题,java.lang.IndexOutOfBoundsException: 检测到不一致

创建一个自定义的LinearLayoutManager

HPLinearLayoutManager.java

public class HPLinearLayoutManager extends LinearLayoutManager {

    public HPLinearLayoutManager(Context context) {
        super(context);
    }

    public HPLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public HPLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    /**
     * Magic here
     */
    @Override
    public boolean supportsPredictiveItemAnimations() {
        return false;
    }
}

创建HPLinearLayoutManager的实例。
HPLinearLayoutManager hpLinearLayoutManager = new HPLinearLayoutManager(mContext);
recyclerView.setLayoutManager(hpLinearLayoutManager);

希望这能帮到你。

1
我尝试了你的方法,但仍然出现异常,不过这个错误的概率已经降低了很多。我将使用你的方式来修复我的应用程序中的问题,希望测试人员不会发现这个错误。 - L. Swifter
@L.Swifter,希望如此 :) - Hiren Patel
它会抛出 IndexOutOfBoundsException - Kuvonchbek Yakubov
反而,对我来说它不起作用,错误出现的一贯性却在我的端口增加。 - Shrut

4

我正在后台线程中修改RecyclerView的数据。我遇到了与OP相同的Exception。在修改数据后,我添加了以下内容:

myRecyclerView.post(new Runnable() { @Override public void run() { myRecyclerAdapter.notifyDataSetChanged(); } });

希望能够帮到您。


3

我遇到了类似的问题,但是跟删除内容相关。我也想保留动画效果。最终我选择使用notifyRemove方法,并将范围传递过去。这似乎修复了所有的问题...

public void deleteItem(int index) {
    try{
        mDataset.remove(index);
        notifyItemRemoved(index);
    } catch (IndexOutOfBoundsException e){
        notifyDataSetChanged();
        e.printStackTrace();
    }
}

看起来正在工作并且解决了IOB异常...


4
为什么你要把它移除两次? - vovkab
他没有两次删除该项。他已经通知了两次。 - Chetan Gaikwad

2
我采用了Cocorico在之前回答中提出的建议(https://dev59.com/mV8d5IYBdhLWcg3wchyi#26927186),使其正常工作,但有一个问题:由于我使用了SortedList,每次数据发生更改(添加、删除等)都使用notifyDataSetChanged()会使你失去通过notifyItemXXXXX(position)获得的项目动画效果。因此,我最终只在批量更改数据时使用它,例如:
public void addAll(SortedList<Entity> items) {
    movieList.beginBatchedUpdates();
    for (int i = 0; i < items.size(); i++) {
        movieList.add(items.get(i));
    }
    movieList.endBatchedUpdates();
    notifyDataSetChanged();
}  

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接