移除项目后,新选择时续订StableIdKeyProvider缓存和RecyclerView / SelectionTracker崩溃

时间:2018-11-28 15:50:43

标签: android android-recyclerview

准备工作:

具有RecyclerView

RecyclerView.Adapter与SQLite Cursor绑定(通过ContentProvider && Loader)。 RecyclerViewRecyclerView.AdapterSelectionTracker链接为design suggestsSelectionTrackerStableIdKeyProvider构建在一起。

第一步-删除项目:

  1. 长按选择RecyclerViews的项目(欢呼SelectionTracker的{​​{1}}),绘制动作栏上下文菜单,然后启动 删除操作,执行SQL删除任务。
  2. SQL删除结束后,使用
    更新Cursor Loader SelectionObserver通话。
  3. restartLoader被解雇,新的onLoadFinished
    获得 Cursor方法RecyclerView.Adapter被调用。
  4. notifyDataSetChanged重绘了RecyclerView.Adapter的内容,看起来很漂亮 好。

第二步-选择其他项目。崩溃:

RecyclerView

删除项目进行中时,我在第一步中看到的内容。 java.lang.IllegalArgumentException at androidx.core.util.Preconditions.checkArgument(Preconditions.java:38) at androidx.recyclerview.selection.DefaultSelectionTracker.anchorRange(DefaultSelectionTracker.java:269) at androidx.recyclerview.selection.MotionInputHandler.selectItem(MotionInputHandler.java:60) at androidx.recyclerview.selection.TouchInputHandler.onLongPress(TouchInputHandler.java:132) at androidx.recyclerview.selection.GestureRouter.onLongPress(GestureRouter.java:96) at android.view.GestureDetector.dispatchLongPress(GestureDetector.java:779) at android.view.GestureDetector.access$200(GestureDetector.java:40) at android.view.GestureDetector$GestureHandler.handleMessage(GestureDetector.java:293) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:193) at android.app.ActivityThread.main(ActivityThread.java:6669) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) StableIdKeyProvider onDetached项目执行内部工作时,看不到适配器中先前分配的ViewHolder的位置:

ViewHolder

void onDetached(@NonNull View view) { RecyclerView.ViewHolder holder = mRecyclerView.findContainingViewHolder(view); int position = holder.getAdapterPosition(); long id = holder.getItemId(); if (position != RecyclerView.NO_POSITION && id != RecyclerView.NO_ID) { int position

这就是为什么RecyclerView在-RecyclerView.NO_POSITION的缓存中包含ID的旧快照而没有删除影响后崩溃的原因。

问题是-为什么?以及如何更新StableIdKeyProvider的缓存?

另一个说明: 当我阅读StableIdKeyProvider代码时,看到了以下评论:

RecyclerView

我不明白这句话的确切含义。也许我遇到了所描述的情况- * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the * next layout pass, the return value of this method will be {#NO_POSITION}. 在不适当的时间打电话了?或者我需要打两次电话?

PS。 对不起,关于文学的描述,有很多复杂的代码

6 个答案:

答案 0 :(得分:7)

我最终开始玩StableIdKeyProvider并切换到我自己的ItemKeyProvider实现:

new ItemKeyProvider<Long>(ItemKeyProvider.SCOPE_MAPPED) {
                    @Override
                    public Long getKey(int position) {
                        return adapter.getItemId(position);
                    }

                    @Override
                    public int getPosition(@NonNull Long key) {
                        RecyclerView.ViewHolder viewHolder = recyclerList.findViewHolderForItemId(key);
                        return viewHolder == null ? RecyclerView.NO_POSITION : viewHolder.getLayoutPosition();
                    }
                }

崩溃消失了,RecyclerView的导航/选择/修改看起来还不错。 怎么样StableIdKeyProvider?..嗯,也许不是为了处理RecyclerView的可变内容而设计的。

答案 1 :(得分:0)

我在StableIdKeyProvider中遇到了相同的问题。编写ItemKeyProvider的自定义实现似乎可以解决问题。这是为RecyclerView构建选择跟踪器时可以使用的基本Kotlin实现:

class RecyclerViewIdKeyProvider(private val recyclerView: RecyclerView)
    : ItemKeyProvider<Long>(ItemKeyProvider.SCOPE_MAPPED) {

    override fun getKey(position: Int): Long? {
        return recyclerView.adapter?.getItemId(position)
                ?: throw IllegalStateException("RecyclerView adapter is not set!")
    }

    override fun getPosition(key: Long): Int {
        val viewHolder = recyclerView.findViewHolderForItemId(key)
        return viewHolder?.layoutPosition ?: RecyclerView.NO_POSITION
    }
}

答案 2 :(得分:0)

我的问题已解决,方法是在“回收”视图适配器中设置setHasStableIds(true)并覆盖getItemId,看来Tracker在适配器中既需要setHasStableIds(true)也需要覆盖getItemId,但出现此错误设置稳定的Id为true后,不覆盖getItemId

 init {
    setHasStableIds(true)
}
override fun getItemId(position: Int) = position.toLong()
override fun getItemViewType(position: Int) = position

答案 3 :(得分:0)

遇到同样的问题,经过长时间的搜索,我找到了答案: 您只需要在“回收站适配器”视图中覆盖该方法即可。

override fun getItemId(position: Int): Long = position.toLong()

答案 4 :(得分:0)

就我而言,问题与ItemDetailsLookup.ItemDetailsViewHolder的初始化有关。事实证明,getAdapterPosition()在绑定ViewHolder时可能返回错误的位置。解决方案是在getAdapterPosition()中的getItemDetails()调用时调用ItemDetailsLookup

答案 5 :(得分:0)

StableIdKeyProvider 不是问题;它做它所说的。

/** Constructor */
public SomeAdapter() {
    this.setHasStableIds(true);
}

适配器确实必须@Override public long getItemId()

@Override
public long getItemId(int position) {
    if(this.mItems == null) {return -1L;}
    return this.mItems.get(position).getId();
}