【问题标题】:Inconsistency detected in RecyclerView, How to change contents of RecyclerView while scrolling在 RecyclerView 中检测到不一致,如何在滚动时更改 RecyclerView 的内容
【发布时间】:2015-01-05 18:40:40
【问题描述】:

我正在使用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)

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

【问题讨论】:

  • 请发布您的适配器代码以及您如何使用它
  • 我将我的解决方案发布在另一个thread

标签: android android-recyclerview


【解决方案1】:

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

旧答案: 似乎是RecyclerView 中的一个错误,据报告herehere。希望它会在下一个版本中得到修复。

【讨论】:

  • 在下一个版本发布之前没有解决方法?
  • 仍然面临问题.. :(
  • 我正在使用 compile 'com.android.support:recyclerview-v7:23.0.0' 但仍然面临同样的问题。?
  • 如果您尝试从后台线程更改列表内容,您将面临同样的问题。
  • @jimmy0251 仍未解决:D 我已经找到了解决这个问题的方法,但是为什么我们不能从另一个线程/活动/片段中更改列表,并且适配器可以接管我们所有的正在调用 notifyDataSet...叹息.....
【解决方案2】:

对我来说没问题。使用 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();
    }
}

【讨论】:

  • 使用notifyDataSetChanged() 时要注意的一点是,默认情况下它不会为更改的项目设置动画,而notifyItemChanged() 会。
  • 当我尝试在适配器中插入项目并在空适配器上使用 notifyItemRangeInserted(0, newNotifications.size()); 时遇到了同样的错误。我通过检查适配器是否为空然后使用notifydatasetchanged 解决了这个问题,否则使用notifyItemRangeInserted(0, newNotifications.size());
  • notifyDataSetChanged 如果您执行 2 件事,也会为项目设置动画 1) 在您的适配器上调用 setHasStableIds(true) 和 2) 覆盖 getItemid 以从您的适配器中为每一行返回一个唯一的长值,一旦您执行此操作,它会触发动画
【解决方案3】:

数据变化时禁止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

【讨论】:

  • 什么是 mIsRefreshing?
  • @satnamsingh 似乎是一个布尔标志,用于指示刷新何时开始但尚未完成。
  • 链接已失效。我在哪里可以 mIsRefreshing = true
【解决方案4】:

尽管在接受的答案中提供了一些关于此问题的有用超链接,RecyclerView 在滚动时的这种行为并不是一个错误

如果您看到此异常,很可能您在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。为消除此问题,应在数据集更改后立即通知适配器。

【讨论】:

    【解决方案5】:

    问题肯定不是因为recyclerview滚动,而是和notifyDataSetChanged()有关。我有一个回收者视图,其中我不断更改数据,即添加和删除数据。每次我向列表中添加项目时,我都会调用 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)
    

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

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

    从那以后,我再也没有遇到过异常。 希望对其他人也一样。 :)

    【讨论】:

      【解决方案6】:

      如果你使用,这个问题就会出现在recyclerview

      adapter.setHasStableIds(true);
      

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

      【讨论】:

        【解决方案7】:

        我遇到了同样的问题,java.lang.IndexOutOfBoundsException: Inconsistency detected

        创建自定义 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);
        

        希望这会对你有所帮助。

        【讨论】:

        • 我尝试了你的答案后仍然得到异常,但是这个错误的概率已经降低了很多。我将在我的应用程序中使用您的方式解决此问题,希望测试人员不要发现错误。
        • @L.Swifter,希望如此:)
        • 它抛出IndexOutOfBoundsException
        • 另一方面,它对我不起作用,发生错误的一致性最终会增加。
        【解决方案8】:

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

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

        希望对你有帮助

        【讨论】:

          【解决方案9】:

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

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

          似乎正在工作并摆脱 IOB 异常...

          【讨论】:

          • 你为什么要删除它两次?
          • 他没有两次删除该项目。他已经通知了两次。
          【解决方案10】:

          我在之前的回答 (https://stackoverflow.com/a/26927186/3660638) 中使用 Cocorico 建议让这个工作,但有一个问题:因为我使用的是 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();
          }  
          

          【讨论】:

            【解决方案11】:

            你必须在你的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);
                    }
                }
            

            【讨论】:

              【解决方案12】:

              创建 CustomLinearLayoutManager:

              public class CustomLinearLayoutManager extends LinearLayoutManager {
              
              public CustomLinearLayoutManager(Context context) {
                      super(context);
                  }
              
                  public CustomLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
                      super(context, orientation, reverseLayout);
                  }
              
                  public CustomLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
                      super(context, attrs, defStyleAttr, defStyleRes);
                  }
              
                  @Override
                  public boolean supportsPredictiveItemAnimations() {
                      return false;
                  }
              
                  public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
                      try {
                          super.onLayoutChildren(recycler, state);
                      } catch (IndexOutOfBoundsException e) {
                          e.printStackTrace();
              
                      }
                  }
              
                  @Override
                  public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
                      try {
                          return super.scrollVerticallyBy(dy, recycler, state);
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
                      return 0;
                  }
              }
              

              【讨论】:

              • 这可能会抑制它...任何观察到的影响?
              【解决方案13】:

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

              1)recyclerview的滚动

              2) 数据集发生变化

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

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

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

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

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

              【讨论】:

                【解决方案14】:

                避免 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());
                }
                

                【讨论】:

                  【解决方案15】:

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

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

                  【讨论】:

                    【解决方案16】:

                    这个问题我也有同样的问题,我很累搜索和解决它。但是我找到了解决的答案,并且没有再次抛出异常。

                    public class MyLinearLayoutManager extends LinearLayoutManager 
                    {
                        public MyLinearLayoutManager(Context context) {
                            super(context);
                        }
                    
                        public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
                            super(context, orientation, reverseLayout);
                        }
                    
                        public MyLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
                            super(context, attrs, defStyleAttr, defStyleRes);
                        }
                    
                        @Override
                        public boolean supportsPredictiveItemAnimations() {
                            return false;
                        }
                    
                        @Override
                        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
                            //override this method and implement code as below
                            try {
                                super.onLayoutChildren(recycler, state);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                    

                    我希望这个答案能解决你的问题。

                    【讨论】:

                      【解决方案17】:

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

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

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

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

                      【讨论】:

                        【解决方案18】:

                        我看不出您发布的代码有什么问题。对我来说唯一奇怪的是这条线

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

                        在适配器的 onBindViewHolder 方法中。当您更改数组中项目列表的大小时,您是否也在更新此数组?

                        【讨论】:

                        • 不,它只是改变了视图的背景。
                        • 我知道它做了什么,我问的是,当您更改适配器上项目的大小时,您是否也在更新 mSelectedArray .. 超出范围的 arrayindex 可能会超出它
                        • 这里是方法代码: private static void setSelected(View view, boolean isSelected) { if (view != null) view.setSelected(isSelected); }
                        • 再一次,我知道它的作用... =>>>> 我问的是,当您更改适配器上项目的大小时,您是否也在更新 mSelectedArray.. 数组索引输出可能会超出范围
                        • 不,我不会更新 mSelectedArray。
                        【解决方案19】:

                        我在尝试使用 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);
                            }
                        }
                        

                        【讨论】:

                          【解决方案20】:

                          声音代码中只有一句话:
                          /** * 当 LayoutState 构造为滚动状态时使用。这应该 * 设置我们可以在不创建新滚动条的情况下进行的滚动量 * 看法。这是高效视图回收所必需的设置。 */ 诠释 mScrollingOffset;

                          【讨论】:

                            【解决方案21】:

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

                            mRecyclerView.scrollToPosition(0)
                            

                            【讨论】:

                              【解决方案22】:

                              我也遇到了同样的问题,我已经通过不使用 notifyItemRangeChanged() 方法解决了这个问题。它在

                              中得到了很好的解释

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

                              【讨论】:

                                【解决方案23】:

                                尝试使用布尔标志,将其初始化为假,并在 OnRefresh 方法中使其为真,如果标志为真,则在将新数据添加到它之前清除您的 dataList,然后将其设为假。

                                你的代码可能是这样的

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

                                【讨论】:

                                  【解决方案24】:

                                  就我而言,问题出在我身上。

                                  我的设置是 Recyclerview、Adapter & Cursor/Loader 机制。

                                  在我的应用程序中的某一时刻,加载程序被破坏了。

                                  supportLoaderManager.destroyLoader(LOADER_ID_EVENTS)

                                  我期待 Recyclerview 会显示一个空列表,因为我刚刚删除了他们的数据源。使错误发现更加复杂的是,该列表是可见的,并且众所周知的异常仅发生在投掷/滚动/动画上。

                                  这花了我几个小时。 :)

                                  【讨论】:

                                    【解决方案25】:

                                    当您想添加视图时执行此操作(如 notifyDataaddView 或类似的东西)

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

                                    【讨论】:

                                      【解决方案26】:

                                      我最近遇到了这个问题,发现我的问题是我在一个循环/调用堆栈上修改适配器的数据源,然后在随后的循环/调用堆栈上调用 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")))
                                      

                                      【讨论】:

                                        猜你喜欢
                                        • 2023-03-03
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 2016-07-17
                                        • 2021-09-30
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 2023-04-05
                                        • 1970-01-01
                                        相关资源
                                        最近更新 更多