RecyclerView错误检测到不一致

时间:2018-09-05 09:10:11

标签: android android-recyclerview recycler-adapter

尽管我知道之前已经问过很多类似的问题,但我认为情况有所不同。一些答案说该错误来自Android库错误,但我使用的API 27可能已修复了该错误。

我将我的问题总结成一个最小的项目。

  

首先,我有一个简单的基本适配器类

abstract class EndlessListAdapter<B : Any> : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    companion object {
        const val VIEW_TYPE_NORMAL = 0x00
        const val VIEW_TYPE_EMPTY = 0x01
        const val VIEW_TYPE_FOOTER = 0x02
    }
    //set this property to update displayed items
    var itemList: List<B> = listOf()
        set(value) {
            val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
                override fun getOldListSize(): Int = field.size
                override fun getNewListSize(): Int = value.size
                override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = areItemsTheSame(field[oldItemPosition], value[newItemPosition])
                override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = areContentsTheSame(field[oldItemPosition], value[newItemPosition])
            })
            field = value
            result.dispatchUpdatesTo(this)
        }
    //set this property to display or hide the footer
    var hasMore: Boolean = false
        set(value) {
            if (value && !field)
                notifyItemInserted(itemList.size)
            else if (!value && field)
                notifyItemRemoved(itemList.size)
        }

    override fun getItemViewType(position: Int): Int {
        return when {
            itemList.isEmpty() -> VIEW_TYPE_EMPTY
            position == itemList.size -> VIEW_TYPE_FOOTER
            else -> VIEW_TYPE_NORMAL
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            VIEW_TYPE_EMPTY -> object : RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.common_empty, parent, false)) {}
            VIEW_TYPE_FOOTER -> object : RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.common_footer, parent, false)) {}
            else -> object : RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.common_default, parent, false)) {}
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {}

    override fun getItemCount(): Int {
        return if (itemList.isEmpty()) 1 else if (hasMore) itemList.size + 1 else itemList.size
    }

    protected abstract fun areItemsTheSame(oldItem: B, newItem: B): Boolean

    protected abstract fun areContentsTheSame(oldItem: B, newItem: B): Boolean
}
  

该活动具有三个按钮和一个RecyclerView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".ActivityMain">

    <Button
        android:id="@+id/button0"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="set 0 items" />

    <Button
        android:id="@+id/button4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="set 4 items" />

    <Button
        android:id="@+id/button8"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="set 8 items" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>
  

Activity类是

class ActivityMain : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        recycler.layoutManager = LinearLayoutManager(this)
        recycler.adapter = ActivityMainAdapter()
        RxView.clicks(button0).observeOn(AndroidSchedulers.mainThread()).subscribe {
            (recycler.adapter as ActivityMainAdapter).let { adapter ->
                adapter.itemList = listOf()
                adapter.hasMore = false
            }
        }
        RxView.clicks(button4).observeOn(AndroidSchedulers.mainThread()).subscribe {
            (recycler.adapter as ActivityMainAdapter).let { adapter ->
                adapter.itemList = listOf(Item(1, "Bob"), Item(2, "Mary"), Item(3, "Ken"), Item(4, "Samantha"))
                adapter.hasMore = false
            }
        }
        RxView.clicks(button8).observeOn(AndroidSchedulers.mainThread()).subscribe {
            (recycler.adapter as ActivityMainAdapter).let { adapter ->
                adapter.itemList = listOf(Item(1, "Jan"), Item(2, "Feb"), Item(3, "Mar"), Item(4, "Apr"), Item(5, "May"), Item(6, "Jun"), Item(7, "Jul"), Item(8, "Aug"))
                adapter.hasMore = true
            }
        }
    }
}

在活动类中,我使用了Item和ActivityMainAdapter类,它们的实现方式如下

class Item(val key: Int, val desc: String)

class ActivityMainAdapter : EndlessListAdapter<Item>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            VIEW_TYPE_NORMAL -> object : RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.activity_main_viewholder, parent, false)) {}
            else -> super.onCreateViewHolder(parent, viewType)
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (getItemViewType(position)) {
            VIEW_TYPE_NORMAL -> holder.itemView.let {
                it.key.text = itemList[position].key.toString()
                it.desc.text = itemList[position].desc
            }
            else -> super.onBindViewHolder(holder, position)
        }
    }

    override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean = oldItem.key == newItem.key

    override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean = oldItem.key == newItem.key && oldItem.desc == newItem.desc
}

当我运行主要活动并单击button4或button8时,出现异常:

09-05 17:08:35.787 4087-4087/demo.p1894 E/AndroidRuntime: FATAL EXCEPTION: main
    Process: demo.p1894, PID: 4087
    java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{3c21c3e position=4 id=-1, oldPos=0, pLpos:0 scrap [attachedScrap] tmpDetached no parent} android.support.v7.widget.RecyclerView{79870ac VFED..... .F....I. 0,432-1080,489 #7f080071 app:id/recycler}, adapter:demo.p1894.ActivityMainAdapter@f449175, layout:android.support.v7.widget.LinearLayoutManager@a01c50a, context:demo.p1894.ActivityMain@6be630b
        at android.support.v7.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:5447)
        at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5629)
        at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5589)
        at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5585)
        at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2231)
        at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1558)
        at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1518)
        at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:610)
        at android.support.v7.widget.RecyclerView.dispatchLayoutStep1(RecyclerView.java:3670)
        at android.support.v7.widget.RecyclerView.onMeasure(RecyclerView.java:3129)
        at android.view.View.measure(View.java:18830)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5954)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1465)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:748)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:630)
        at android.view.View.measure(View.java:18830)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5954)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.support.v7.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:139)
        at android.view.View.measure(View.java:18830)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5954)
        at android.support.v7.widget.ActionBarOverlayLayout.onMeasure(ActionBarOverlayLayout.java:400)
        at android.view.View.measure(View.java:18830)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5954)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at android.view.View.measure(View.java:18830)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5954)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1465)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:748)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:630)
        at android.view.View.measure(View.java:18830)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5954)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
        at com.android.internal.policy.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2643)
        at android.view.View.measure(View.java:18830)
        at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2136)
        at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1248)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1484)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1139)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6064)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:860)
        at android.view.Choreographer.doCallbacks(Choreographer.java:672)
        at android.view.Choreographer.doFrame(Choreographer.java:608)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:846)
        at android.os.Handler.handleCallback(Handler.java:742)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:157)
        at android.app.ActivityThread.main(ActivityThread.java:5653)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Z

1 个答案:

答案 0 :(得分:0)

我自己解决了。这是由于空视图与DiffUtil冲突所致。

在使用DiffUtil类时,在回收者视图上具有页眉,页脚或空白视图时,这是一个常见错误。