从回收者视图中删除多个(50+)项目会引发异常(已附加Stacktrace)

时间:2019-01-20 16:40:46

标签: android android-studio android-recyclerview

以下是用动画定义的回收者视图:

mRecyclerView = rootView.findViewById(R.id.list1);
mRecyclerView.setLayoutManager(new GridLayoutManager(fragmentActivity, 2));
mRecyclerView.setHasFixedSize(true);
mAdapter = new MediaGridAdapter(fragmentActivity, mediaModels);

MyScaleInAnimator animator = new MyScaleInAnimator(mAdapter);
animator.setAddDuration(100);
animator.setRemoveDuration(100);
mRecyclerView.setItemAnimator(animator);

以下是我如何从“回收者”视图中删除项目的信息:

public void removeData(String fileType, int position, Context context) {
   mediaModels.remove(position);
   notifyItemRemoved(position);
}

(当我删除多个项目时,此方法称为多次迭代)。 问题: 当我从“回收者”视图中选择少量项目时,上述删除方法将按预期运行。但是,如果我选择大量项目(50-100)并立即删除,则会引发以下异常:

E/AndroidRuntime: FATAL EXCEPTION: main
                 Process: com.akl.alldrive, PID: 26558
                 java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{f308d5b position=54 id=-1, oldPos=74, pLpos:54 scrap [attachedScrap] tmpDetached no parent} android.support.v7.widget.RecyclerView{7b12cb3 VFED.V... ......I. 0,147-1080,1857 #7f0a0099 app:id/list1}, adapter:jp.wasabeef.recyclerview.adapters.ScaleInAnimationAdapter@780c270, layout:android.support.v7.widget.GridLayoutManager@fb119e9, context:com.akl.alldrive.activities.BaseActivity@2c3cc96
                     at android.support.v7.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:5715)
                     at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5898)
                     at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5858)
                     at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5854)
                     at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2230)
                     at android.support.v7.widget.GridLayoutManager.layoutChunk(GridLayoutManager.java:557)
                     at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1517)
                     at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:612)
                     at android.support.v7.widget.GridLayoutManager.onLayoutChildren(GridLayoutManager.java:171)
                     at android.support.v7.widget.RecyclerView.dispatchLayoutStep1(RecyclerView.java:3875)
                     at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:3639)
                     at android.support.v7.widget.RecyclerView.consumePendingUpdateOperations(RecyclerView.java:1888)
                     at android.support.v7.widget.RecyclerView$1.run(RecyclerView.java:407)
                     at android.view.Choreographer$CallbackRecord.run(Choreographer.java:966)
                     at android.view.Choreographer.doCallbacks(Choreographer.java:778)
                     at android.view.Choreographer.doFrame(Choreographer.java:710)
                     at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:952)
                     at android.os.Handler.handleCallback(Handler.java:789)
                     at android.os.Handler.dispatchMessage(Handler.java:98)
                     at android.os.Looper.loop(Looper.java:164)
                     at android.app.ActivityThread.main(ActivityThread.java:6798)
                     at java.lang.reflect.Method.invoke(Native Method)
                     at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
                     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #4
                 Process: com.akl.alldrive, PID: 26558
                 java.lang.RuntimeException: An error occurred while executing doInBackground()
                     at android.os.AsyncTask$3.done(AsyncTask.java:353)
                     at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383)
                     at java.util.concurrent.FutureTask.setException(FutureTask.java:252)
                     at java.util.concurrent.FutureTask.run(FutureTask.java:271)
                     at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
                     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
                     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
                     at java.lang.Thread.run(Thread.java:764)
                  Caused by: java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling android.support.v7.widget.RecyclerView{7b12cb3 VFED.V... ......I. 0,147-1080,1857 #7f0a0099 app:id/list1}, adapter:jp.wasabeef.recyclerview.adapters.ScaleInAnimationAdapter@780c270, layout:android.support.v7.widget.GridLayoutManager@fb119e9, context:com.akl.alldrive.activities.BaseActivity@2c3cc96
                     at android.support.v7.widget.RecyclerView.assertNotInLayoutOrScroll(RecyclerView.java:2880)
                     at android.support.v7.widget.RecyclerView$RecyclerViewDataObserver.onItemRangeRemoved(RecyclerView.java:5308)
                     at android.support.v7.widget.RecyclerView$AdapterDataObservable.notifyItemRangeRemoved(RecyclerView.java:12032)
                     at android.support.v7.widget.RecyclerView$Adapter.notifyItemRemoved(RecyclerView.java:7231)
                     at com.akl.alldrive.adapters.MediaGridAdapter.removeData(MediaGridAdapter.java:60)

如何解决这个问题?

4 个答案:

答案 0 :(得分:1)

  

IndexOutOfBoundsException

如果您用尽了迭代的“界限”,则会抛出

。要在recyclerview中修复,您需要实现一种机制,无论何时删除,都将项目位置向上移动,因此它仍将位于“界限”内。

使用notifyItemRangeChanged(position, getItemCount());

摘自文档:“在获取此方法内的相关数据项时,您仅应使用position参数,而不应保留其副本。如果以后需要某个项目的位置(例如,在点击监听器中),使用RecyclerView.ViewHolder.getAdapterPosition(),它将具有更新的适配器位置。”

答案 1 :(得分:0)

与其一次删除一个项目,然后通知已删除的每个项目,不如一次尝试全部完成。

使用某些MediaModel类的示例:

ArrayList<MediaModel> itemsToRemove = /* however you obtain your list of items to remove */;

medialModel.removeAll(itemsToRemove);

notifyDataSetChanged();

答案 2 :(得分:0)

发生java.lang.IndexOutOfBoundsException错误是因为您正在尝试从适配器中删除不存在的项目。尝试删除非连续项目时,您不能依赖于项目索引位置。例如,当您尝试顺序删除大小为10的列表中的1、3、8和9索引位置上的项目时,将显示错误IndexOutOfBoundsException,并且在删除位置1和3后停止。而且您实际上并没有删除正确的项目,因为在位置1删除项目后,位置3的上一个项目现在移到了位置2。

您可以使用 id 作为商品标识符来解决此问题。如果没有ID,请更改数据模型:

public class MediaModel {
  private int id;

  // your other data

  // setter and getter

}

然后,在删除项目时,请检查ID而不是位置。像这样:

public void removeData(String fileType, int id) {
    int position = -1; // no item found
    for (int i = 0; i < mediaModels.size(); i++) {
      MediaModel model = mediaModels.get(i);
      if(model.getId() == id) {
         position = i;
         break; // we have found the item, so stop searching.
      }

      if(position >= 0) {
         mediaModels.remove(position);
         notifyItemRemoved(position);
      }
    }
}

答案 3 :(得分:0)

代码中的问题是您尝试通过位置访问mediaModels中的数据。在某些情况下,这将导致索引超出范围,因为调用mediaModels.remove(position)时会从中删除元素该位置并调整/移动其他元素,例如,如果我在mediaModels中有5个元素:汉堡包,三明治,比萨饼,冰淇淋,热狗...,并且我将mediaModels.remove(position)位置设为1,则删除三明治并转移比萨饼,冰淇淋,热狗填补了空的位置,所以现在mediaModels的大小现在是4,不再是5。如果我随后在位置为4的地方调用mediaModels.remove(position),则由于IndexOutOfBoundsException的大小mediaModels列表已更改。

因此,您需要做的是在循环中将要删除的元素与mediaModels中的元素进行比较,以找到所需的确切位置,然后将该位置值放入:

mediaModels.remove(i);
notifyItemRemoved(i);

我将方法参数位置替换为targetElement,并假设它是一个String,仅用于说明,因此您的代码应如下所示:

public void removeData(String fileType, String targetElement, Context context) 
{
    for(int i=0;i<mediaModels.size();++i) {
        if(mediaModels.get(i).equals(targetElement){//we have found the position                                           
           mediaModels.remove(i);
           notifyItemRemoved(i);
           break;
        }
    }
}