如何修复ViewPager2中的“设计假设被违反”错误?

24

我正在使用ViewPager2、Kotlin和AndroidX。当适配器不在索引0处时,如果我更改适配器列表并将当前项目设置为索引0,则会抛出异常。

java.lang.IllegalStateException: Design assumption violated.
        at androidx.viewpager2.widget.ViewPager2.updateCurrentItem(ViewPager2.java:538)
        at androidx.viewpager2.widget.ViewPager2$4.onAnimationsFinished(ViewPager2.java:518)
        at androidx.recyclerview.widget.RecyclerView$ItemAnimator.isRunning(RecyclerView.java:13244)
        at androidx.viewpager2.widget.ViewPager2.onLayout(ViewPager2.java:515)
        at android.view.View.layout(View.java:15596)

在 ViewPager2 的第 537 行存在一个 if 语句,该语句导致异常:

在ViewPager2的第537行有一个if语句,它引起了异常:

        // Extra checks verifying assumptions
        // TODO: remove after testing
        View snapView1 = mPagerSnapHelper.findSnapView(mLayoutManager);
        View snapView2 = mLayoutManager.findViewByPosition(snapPosition);
        if (snapView1 != snapView2) {
            throw new IllegalStateException("Design assumption violated.");
        }

这是我更新适配器列表的方式:

adapter.list = newList
adapter.notifyDataSetChanged()
viewpager.setCurrentItem(0, true)

只有当 smoothScroll 被设置为 true 时才会发生这种情况。

我错过了什么?

编辑:我正在使用 com.google.android.material:material:1.1.0-alpha08 中提供的 androidx.viewpager2.widget.ViewPager2


我发现将适配器的列表设置为新列表会自动将分页器移动到第一项...所以在调用setCurrentItem时实际上没有什么可以滚动的。 - user11566289
@glucaio 当新列表的项数比以前的列表多时,索引不会设置为0。 - Amir
你是正确的。我也看到了这个问题。可能要提交一个错误报告吗?设置当前项目肯定仍然是正在进行中的工作。 - user11566289
@Amir,你能分享一下你的代码示例项目作为git仓库吗?这样我们就可以尝试帮助你了。 - AskNilesh
5个回答

25

我在配置更改时遇到了与ViewPager2相同的问题。我正在使用:

implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.viewpager2:viewpager2:1.0.0-beta03'
在我的情况下,我已经覆盖了FragmentStateAdapter类中的getItemId()方法。当我删除了getItemId()方法后,"IllegalStateException: Design assumption violated"错误就不再出现了! :-)

43
如果遇到此问题但无法删除自定义的 getItemId() 实现,则应该覆盖 containsItem(long)。这在 Javadocs 中有说明。 - Patrick Grayson
3
@PatrickGrayson的评论是这个问题的真正答案。 - Paulo Cesar

19

我正在使用androidx.viewpager2:viewpager2:1.0.0

对于仍然困在这里并需要实现getItemId()containsItem()的人,请检查您的getItemId()containsItem()实现。我的问题是当我提供10个具有可能有相同值的几个项时,就会出现此错误。

媒体具有多个具有相同值的数据。

private var mediaId = media.map { it.hashCode().toLong() }

override fun getItemId(position: Int): Long = mediaId[position]

override fun containsItem(itemId: Long): Boolean = mediaId.contains(itemId)

当您更新数据或者简单地进行滑动时,ViewPager变得混乱,因为您实现了具有非唯一ID的getItemId()方法。解决方案就是确保您的数据是唯一的或者拥有它们自己的ID。*我的媒体数据没有ID。

如果您打开getItemId()的实现,会发现注释中有这部分内容:

  • @return stable item id {@link RecyclerView.Adapter#hasStableIds()}

您需要为每个项都有一个唯一的ID。


我已经实现了getItemId(),这是必须的吗?我正在使用Material 1.3.0。 - Mahmoud Mabrok
如果您的数据中每个数据都有唯一的ID或者您可以确保您的数据不会重复,那么您就不必执行此操作。 - Yehezkiel L

0

这是一个简单用例的解决方案

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter

class ViewPager2Adapter(
val dataList: List<Item> = listOf(),
fragmentActivity: FragmentActivity
) :
FragmentStateAdapter(fragmentActivity) {

val pageIds= dataList.map { it.hashCode().toLong() }.toMutableList()

override fun createFragment(position: Int): Fragment {
    return MyFragment.newInstance(dataList[position])
}

override fun getItemCount(): Int {
    return dataList.size
}

override fun getItemId(position: Int): Long {
    return pageIds[position]
}

override fun containsItem(itemId: Long): Boolean {
    return pageIds.contains(itemId)
}
}

然后我遇到了一个特定的用例,当我用一个新对象替换位置上的对象时,我遇到了异常。

解决方案: 现在,如果您正在对适配器进行数据更改,即使用新对象替换dataList中的对象,则请确保将旧对象的pageIds中的id替换为新对象的id。

(viewPager2.adapter as? ViewPager2Adapter)?.apply {
pageIds[oldIndex] = (NewItemObject).hashCode().toLong()
notifyItemChanged(oldIndex)
}

0

我曾经遇到过同样的问题,但是在当前版本androidx.viewpager2:viewpager2:1.0.0-beta01中,这个bug似乎已经被修复了(至少对我来说是这样)。

更多信息请参见ViewPager2 1.0.0-beta01发布说明


我正在使用androidx.viewpager2.widget.ViewPager2,它可在com.google.android.material:material:1.1.0-alpha08中获得。 - Amir
2
这是与com.google.android.material:material:1.1.0-alpha08相同的ViewPager2库,依赖于androidx.viewpager2:viewpager2:1.0.0-alpha06。您必须等待,直到Material Components库更新为使用androidx.viewpager2:viewpager2:1.0.0-beta01,或者您可以强制使用当前的ViewPager2库版本,即implementation("androidx.viewpager2:viewpager2:1.0.0-beta02") - xsveda

-1
我的问题是我正在自定义containsItem(),以便在某些条件下从ViewPager中删除片段,然后返回false
在旋转手机(然后滑动ViewPager2)后,我遇到了“设计假设违反”的问题。
我通过只返回超类方法来解决这个问题:return super.containsItem(itemId);
@Override
public boolean containsItem(long itemId) {
    // Your code
    return super.containsItem(itemId);
}

更新:

containsItem方法没有"CallSuper"注解,因此不需要被调用

这是正确的,不需要调用super;在返回布尔值之前,我没有显式地调用它,只需返回它即可解决此问题。


containsItem方法没有“CallSuper”注释,因此不需要被调用。 - DennisVA
@DennisVA,顺便说一下,你不必去调用它。但是你必须返回一个布尔值,并且每当你不想明确地返回true/false时,你就返回超类方法。我的问题是每当我返回一个明确的值时就会出现问题,所以我通过返回超类方法来解决它! - Zain
如果只是调用了父类,那么这段代码块就相当于被移除了,对吗? - chitgoks
@chitgoks 我没听懂你的意思... 你是要调用父类并返回什么? - Zain
@zain 我的意思是,如果你只调用 return super.containsItem(itemId); 那么这就相当于删除了该方法。因此没有必要加上它。 - chitgoks
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接