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

时间:2014-11-09 10:31:39

标签: android android-recyclerview

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

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

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

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

这是Exception的完整堆栈跟踪:

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)

AdapterView代码:

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;
    }
}

24 个答案:

答案 0 :(得分:37)

修改:现在修复了该错误,如果您仍然遇到相同的异常,请确保仅从主线程更新您的适配器数据源并调用适当的适配器通知方法。

旧答案: RecyclerView似乎是一个错误,它已报告herehere。希望它将在下一个版本中修复。

答案 1 :(得分: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 :(得分:10)

当数据发生变化时,只需禁止RecyclerView的滚动。

和我的代码一样:

mRecyclerView.setOnTouchListener(
        new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (mIsRefreshing) {
                    return true;
                } else {
                    return false;
                }
            }
        }
);

更多关于:http://drakeet.me/recyclerview-bug-indexoutofboundsexception-inconsistency-detected-invalid-item-position-solution

答案 3 :(得分:7)

虽然在接受的答案中给出了关于此问题的几个有用的超链接,滚动时echo $this->Form->select('field', $options); 的这种行为是错误的

如果您看到此异常,很可能在RecyclerView的内容被更改后,您忘记通知适配器&#34;。只有在将项目添加到数据集后,人们才会调用RecyclerView。但是,不仅在重新填充适配器后发生不一致,而且当您删除项目或清除数据集时,应通过向适配器通知此更改来刷新视图:

notifyDataSetChanged()

就我而言,我尝试在public void refillAdapter(Item item) { adapter.add(item); notifyDataSetChanged(); } public void cleanUpAdapter() { adapter.clear(); notifyDataSetChanged(); /* Important */ } 中清理适配器,并在onStop()中重新填充。在使用onStart()清理适配器后,我忘了拨打notifyDataSetChanged()。然后,每当我将状态从clear()更改为onStop()并在数据集重新加载时快速滚动onStart(),我就会看到此异常。如果我在没有滚动的情况下等待重新加载结束,那么没有异常,因为这次可以顺利恢复适配器。

简而言之,RecyclerView在视图更改时不一致。如果您在处理数据集中的更改时尝试滚动视图,则会看到RecyclerView。要消除此问题,应在更改数据集后立即通知适配器。

答案 4 :(得分:5)

问题绝对不是因为recyclerview滚动,而是与 notifyDataSetChanged()有关。我有一个回收站视图,我不断更改数据,即添加和删除数据。我每次在列表中添加项目时都会调用 notifyDataSetChanged()但是每当删除项目或清除列表时都没有刷新适配器

所以要解决:

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

我在list.clear()之后调用 adapter.notifyDataSetChanged(),无论它在何处需要。

var_dump

从那时起,我从未遇到异常。 希望它也适用于其他人。 :)

答案 5 :(得分: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);

希望这会对你有所帮助。

答案 6 :(得分:3)

我有类似的问题,但删除了内容。我也想保留动画。我最终使用了notifyRemove,然后传递了范围。这似乎解决了任何问题......

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

似乎正在工作并摆脱IOB例外......

答案 7 :(得分:3)

如果您使用

,则会出现此问题
adapter.setHasStableIds(true);

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

答案 8 :(得分:3)

我发现,对我来说,当两件事同时发生时,就会发生这种异常,即

1)滚动recyclelerview

2)数据集发生变化

所以,我通过禁用滚动来解决这个问题,直到调用notifydatasetchanged。

leaderAdapter.notifyDataSetChanged();
pDialog.hide();

要禁用滚动,我使用了一个进度对话框,其setCancelable为false。

pDialog = new ProgressDialog(getActivity());
pDialog.setMessage("Please wait...");
pDialog.setCancelable(false);

这里的技巧是仅在数据集更新后启用滚动。

答案 9 :(得分:2)

我在之前的回答(https://stackoverflow.com/a/26927186/3660638)中使用Cocorico建议工作了但是有一个问题:因为我使用的是SortedList,每次数据发生变化时都使用notifyDataSetChanged()添加,删除等等会让你丢失notifyItemXXXXX(position)带来的项目动画,所以我最后做的就是在批量更改数据时使用 ,例如:< / p>

public void addAll(SortedList<Entity> items) {
    movieList.beginBatchedUpdates();
    for (int i = 0; i < items.size(); i++) {
        movieList.add(items.get(i));
    }
    movieList.endBatchedUpdates();
    notifyDataSetChanged();
}  

答案 10 :(得分:2)

你必须在你的getitem计数中使用

public int getItemCount() {

            if (mList!= null)
                return mList.size();
            else
                return 0;
        }

同时刷新回收者视图请使用此

if (recyclerView.getAdapter() == null) {

            recyclerView.setHasFixedSize(true);
            mFileListAdapter= new FileListAdapter(this);
            recyclerView.setAdapter(mFileListAdapter);
            recyclerView.setItemAnimator(new DefaultItemAnimator());
        } else {
            mFileListAdapter.notifyDataSetChanged();        

        }

通过使用此解决方案,您无法解决问题 你只需在onBindViewHolder中使用一个条件来解析java.lang.IndexOutOfBoundsException

 public void onBindViewHolder(FileHolder fileHolder, final int i) {
        if(i < mList.size)
        {
           String name = mList.get(i);       
           setSelected(fileHolder.itemView, mSelectedArray.get(i));
           fileHolder.mTextView.setText(name);
        }
    }

答案 11 :(得分:2)

我正在改变后台RecyclerViewThread的数据。我得到了与OP相同的Exception。我在更改数据后添加了这个:

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

希望有所帮助

答案 12 :(得分:1)

当我根据一些选择标准设置新的适配器实例时,我遇到了同样的问题。

我已使用RecyclerView.swapAdapter(adapter, true)解决了我的问题 当我们设置新适配器时。

答案 13 :(得分:1)

避免使用notifyDatasetHasChanged()并执行以下操作:

public void setItems(ArrayList<Article> newArticles) {
    //get the current items
    int currentSize = articles.size();
    //remove the current items
    articles.clear();
    //add all the new items
    articles.addAll(newArticles);
    //tell the recycler view that all the old items are gone
    notifyItemRangeRemoved(0, currentSize);
    //tell the recycler view how many new items we added
    notifyItemRangeInserted(0, articles.size());
}

答案 14 :(得分:1)

我已经复制了这个问题。 当我们从mList中删除后台线程中的项目但不调用notifyDataSetChanged()时会发生这种情况。现在如果我们滚动这个异常就要来了。

java.lang.IndexOutOfBoundsException:检测到不一致。无效的项目位置86(偏移:86).state:100

最初,我有100个项目,并从后台线程中删除了一些项目。

似乎Recyclerview调用getItemCount()本身来验证状态。

答案 15 :(得分:0)

尝试使用布尔标志,将其初始化为false,并在OnRefresh方法中将其设为true,如果flag在向其添加新数据之前为true,则清除dataList,之后将其设为false。

您的代码可能就像这样

 private boolean pullToRefreshFlag = false ;
 private ArrayList<your object> dataList ;
 private Adapter adapter ;

 public class myClass extend Fragment implements SwipeRefreshLayout.OnRefreshListener{

 private void requestUpdateList() {

     if (pullToRefresh) {
        dataList.clear
        pullToRefreshFlag = false;
     }

     dataList.addAll(your data);
     adapter.notifyDataSetChanged;


 @Override
 OnRefresh() {
 PullToRefreshFlag = true
 reqUpdateList() ; 
 }

}

答案 16 :(得分:0)

我也有同样的问题,我已经修复了它,没有使用notifyItemRangeChanged()方法。

很好地解释了它

https://code.google.com/p/android/issues/detail?id=77846#c10

答案 17 :(得分:0)

我发现你发布的代码没有出现问题。对我来说唯一奇怪的是这一行

setSelected(fileHolder.itemView, mSelectedArray.get(i));

在适配器的onBindViewHolder方法中..当你更改数组中项目列表的大小时,你也在更新这个数组吗?

答案 18 :(得分:0)

就我而言,问题是我。

我的设置是Recyclerview,Adapter&amp;光标/加载器机制。

在我的应用程序的某一点上,加载程序被破坏了。

supportLoaderManager.destroyLoader(LOADER_ID_EVENTS)

我期待Recyclerview会显示一个空列表,因为我刚刚删除了他们的数据源。使错误发现更复杂的原因是,列表是可见的,并且只有在fling / scroll / animation上才会出现众所周知的异常。

这花了我几个小时。 :)

答案 19 :(得分:0)

在我的情况下,它通过改变解决了 mRecyclerView.smoothScrollToPosition(0)

mRecyclerView.scrollToPosition(0)

答案 20 :(得分:0)

声码中有一句话:
        / **          *在LayoutState以滚动状态构造时使用。这应该          *设置我们可以在不创建新内容的情况下进行滚动的数量          *观点。设置这是高效视图回收所必需的。          * /         int mScrollingOffset;

答案 21 :(得分:0)

当我尝试使用notifyItemInserted方法将第一项添加到recyclerView时,我遇到了类似的问题,所以我在我的适配器上修改了addItem函数,如下所示,它已解决。

奇怪的问题,希望很快就能解决这个问题!

public void addItem(int position, TableItem item) {
    boolean firstEntry = false;
    if (items.size() == 0) {
        firstEntry = true;
    }

    items.add(position, item);

    if (firstEntry) {
        notifyDataSetChanged();
    } else {
        notifyItemInserted(position);
    }
}

答案 22 :(得分:0)

当您想添加视图时(例如 notifyData addView 或类似的东西)执行此操作< / p>

if(isAdded()){ 
    // 
    //  add view like this.
    //
    //  celebrityActionAdapter.notifyItemRangeInserted(pageSize, 10);
    //
    //
}

答案 23 :(得分:0)

我最近遇到了这个问题,发现我的问题是我正在一个循环/调用堆栈上修改适配器的数据源,然后在随后的循环/调用堆栈上调用notifyDataSetChanged。在更改数据源和发生notifyDataSetChanged之间,RecyclerView试图通过滚动来填充视图,并注意到适配器处于怪异状态并合理地抛出了此异常。

Yigit Boyar explains over and over again导致您的应用程序崩溃的两个原因:

  • 更改适配器源和notifyDataSetChanged()
  • 时,您必须位于同一调用堆栈上
  • 更改适配器的数据源时,您必须位于主线程上

如果不确定如何调试,请在更改适配器源和调用notifyDataSetChanged

的位置添加以下Kotlin代码。
Log.d("TEST", "isMainThread: ${Looper.myLooper() == Looper.getMainLooper()}")
Log.d("TEST", Log.getStackTraceString(Exception("Debugging RV and Adapter")))