ViewPager2 默认位置

42

我正在使用ViewPager2创建幻灯片。比如,这个幻灯片有三项内容,我希望在活动打开时显示第二项内容。我使用了setCurrentItem(int item, boolean smoothScroll)方法,但它没有起作用,什么都没有发生。我该如何实现呢?

viewPager.adapter = adapter
viewPager.setCurrentItem(1, true)

第二个项目是1。从0开始计数。 - shb
@shb 谢谢回复,但我写的示例代码是错误的。在我的代码中,这个是1,但它不起作用。 - hosseinAmini
你可能想分享更多的代码。你分享的代码运行得非常好。请分享你的viewPager适配器类,以及你从哪里调用它。 - shb
13个回答

60

我认为更容易、更可靠的解决方法是推迟到下一次运行周期,而不是使用不安全的延迟,例如:

viewPager.post {
  viewPager.setCurrentItem(1, true)
}

在浪费了数天时间寻找 我的 代码中的错误后,我独立地找到了这个解决方案。提示:如果其他方法都失败了,请尝试将保存初始项目位置的字段设置为 final。【不要问我为什么!】 - Bad Loser
6
你也可以使用viewPager.doOnAttach { ... }而不是viewPager.post{ ... },这可能会更清晰地显示发生了什么,因为问题似乎与运行周期无关,而与viewPager未附加到窗口有关。请参见Davide的解释。 - josias
拯救了我的生命。由于这个问题,推出延迟了3个小时。非常感谢。 - uddi baba
@josias你的解决方案对我不起作用,但答案确实有效 ;) - Sir NIkolay Cesar The First
这对我没有起作用,但是在一个 launch {} 块内运行 viewPager.setCurrentItem(1, true) 解决了这个问题。(使用 Kotlin) - undefined

25

setCurrentItem(int item, boolean smoothScroll)ViewPager中正常工作,但在ViewPager2中并不如预期。最终,我通过将setCurrentItem(int item, boolean smoothScroll)方法添加到延迟中来解决了这个问题,如下所示:

Handler().postDelayed({
     view.viewPager.setCurrentItem(startPosition, false)
}, 100)

7
这个方法对我也管用。但是对我来说,这不是一个解决方案,而是一个hack(谁知道要让它可靠地工作需要多长时间的延迟)。这是绕过有缺陷的Android组件的hack...希望Google能尽快修复它,这样我们就不需要再去hack了... - Jiří Křivánek
1
我实际上花了很多时间试图弄清楚为什么在调用此函数时我的选项卡没有被更改。很糟糕,我们需要为基本功能寻找解决方法。 - Vucko
4
尽管我在我的帖子中已经解释了这个答案的问题,但我会在这里提一下原因来证明我投票反对的理由。这很简单,你为什么要随意延迟事情呢?这对最终用户非常不利,他们可能需要等待1/10秒,而他们的手机可能更快,或者有一个较慢的手机,需要更长时间准备好事情,从而导致错误出现。很遗憾你的答案被接受了。 - Davide Cannizzo

14

首先,我认为被接受的答案不应该是@hosseinAmini的,因为它建议使用延迟来解决问题。你应该首先寻找引起假定错误的原因,而不是信任像那样不合理的解决方案。

@Rune的提议是正确的,所以我在我的答案中引用他们的代码:

...

viewPager.post {
    viewPager.setCurrentItem(1, true)
}
我唯一要争论的是上述观点认为他们的解决方案只是将该lambda推迟到下一个运行周期执行。这样做并不能使任何错误的工作正常运行。实际上,所做的是将该lambda的执行推迟到视图附加到窗口后,这意味着它也已经添加到了父视图中。确实,在附加到窗口之前更改当前 ViewPager2 项存在问题。以下是一些支持此说法的证据:
  • 使用任何一个Handler都不会像这样有效。

Handler(Looper.getMainLooper()).post {
    viewPager.setCurrentItem(1, true) // Not working properly
}

从理论上讲,由于ViewPager2已连接到窗口并获得了主Looper消息队列中的优先级,因此可能偶然起作用,但不应该依赖它,因为没有保证会起作用(甚至更有可能不起作用),即使它确实有效,进一步调查运行多个测试也应该能够说明我的观点。

View.handler获取null,这意味着该视图尚未附加到任何窗口。

View.handler // = null
尽管 Android UI 与主线程的主循环绑定在一起,这将始终唯一对应于主线程,因此也称为 UI 线程,但奇怪的设计选择在于处理程序在视图附加到窗口之前不与每个视图相关联。这可能是因为视图在未成为层次结构的一部分时无法在主线程上安排任何工作,这在实现调度视图更新的视图控制器时可能很有用(在这种情况下,它将使用 View 的处理程序(如果有)-或者如果没有,则跳过要调度的任务)。

viewPager.doOnAttach {
    viewPager.setCurrentItem(1, true)
}

感谢您的建议!它更好地表达了实际意图,而不是依赖于 View.post 方法的行为。


14

不要使用定时器,因为可能会遇到很多概率状态,在这些状态中,用户可能拥有一个运行时间比100ms长得多的缓慢手机,此外,您也不希望定时器过慢,使其变得非常不可靠。

以下是我们所做的,我们设置一个听众到我们的ViewTreeObserver,并等待一组孩子已经在我们的ViewPager2的RecyclerView布局完成(它的内部工作)。 一旦我们确定x个项目已经被布置,我们开始我们的无动画滚动以开始位置。

val recyclerView = (Your ViewPager2).getChildAt(0)

recyclerView.apply {
   val itemCount = adapter?.itemCount ?: 0
   if(itemCount >= #(Position you want to scroll to)) {
      viewTreeObserver.addOnGlobalLayoutListener(object: ViewTreeObserver.OnGlobalLayoutListener {
      override fun onGlobalLayout() {
         viewTreeObserver.removeOnGlobalLayoutListener(this)

         // False for without animation scroll
         (Your ViewPager2).scrollToPosition(#PositionToStartAt, false)
      }
   }
}

11
不要使用计时器和所有那些与“post”有关的东西,这不是可靠的解决方案,只是一段有问题的代码。 相反,尝试使用viewPager.setCurrentItem(1, false)。 这里的“false”是关于平滑滚动的,当您的活动刚打开时,您无法平滑滚动您的viewPager2。 在onViewCreated()方法中的片段上进行了测试,它也不能与“true”一起工作,但可以与“false”一起工作。

当我在页面开头设置“false”时,“yes”对我来说运行良好。 - Mahmoud Ayman
嗯,在我的情况下,当使用 true 时,我的片段没有被调用 onResume,这搞乱了事情。使用 false 就可以正常工作。显然是 ViewPager2 的一个 bug,但至少我们有一个可行的解决方案... - algrid

7
如上所述,您需要在 ViewPager2 上使用 setCurrentItem(position,smoothScroll)方法来显示所选项目。为使其正常工作,您必须定义一个回调,这是一个示例:
ViewPager2.OnPageChangeCallback callback = new ViewPager2.OnPageChangeCallback() {
    @Override
    public void onPageSelected(int position) {
        super.onPageSelected(position);
    }
};

然后,您需要按照以下步骤进行注册
viewPager.registerOnPageChangeCallback(callback);

英译中:

同时也不要忘记取消注册它:

viewPager.unregisterOnPageChangeCallback(callback);

当你调用setCurrentItem(position)方法时,它会从你的回调中调用onPageSelected(int position)方法,传递你的参数,然后从FragmentStateAdapter类中调用createFragment(int position)方法来显示你的fragment

不幸的是,这正是我所做的,但它不起作用(版本2可能仍有错误)。实际上,回调被正确调用,但适配器被要求创建第一个片段(大多数情况下是如此,有时它会工作 - 因此,在基于事件驱动的Android方法中可能存在竞争条件)。使用版本1.0.0... - Jiří Křivánek
我建议您遵循这篇文章:https://www.raywenderlich.com/8192680-viewpager2-in-android-getting-started - Ibrokhim Kholmatov

5

我曾经尝试在 Handler().dely()viewPager2.post{} 以及 'viewPager2.get(0).post 中改变 viewpager2 页面,但都无法生效。 我使用的是带有 FragmentStateAdapterTablayoutViewPager

最终解决方法是,在手动将 FragmentStateAdapter 绑定到 yourViewPager2View.adapter 后,改变 RecyclerViewViewPager2 中的位置

 (yourViewPager2View[0] as RecyclerView).scrollToPosition(moveToTabNumber)

为什么

我的问题是在FragmentStateAdapter中,onCreateFragment(position:Int):Fragmeet函数总是以0位置开始显示fragment,无论我将页码设置为多少。

viewPager.setCurrentItem = pageNumber

我在FragmentStateAdapter中检查了它的调用,它是在FragmentStateAdapter中被调用的:

onBindViewHolder(final @NonNull FragmentViewHolder holder, int position)`

所以我所需的就是强制 onBindViewHolder 调用 onCreateFragment(position:Int) 并传入我想要的页面编号。

4
在视图出现之前,您可以尝试使用viewPager.setCurrentItem(pageNumber, false)。在我的情况下,我在片段的onViewCreated方法中调用了这些方法。希望这对您也能起作用。如果将第二个参数设置为“true”,则该方��调用将无效,原因未知。 - nadinnnnnne
1
非常感谢!你的技巧帮助我解决了另一个ViewPager2的谜团:当x=0时,setCurrentItem(x,false)会导致onPageSelected()被调用两次;有趣的是,当x!=0时,一切都按预期工作。 - DmitryO.

4

mViewPager.setCurrentItem(1, true); ---> 如您之前所述,这就足够了。

应该可以正常工作,如果有疑问,请检查您的位置:

        @Override
        public void onPageSelected(int i) {
            if (LOG_DEBUG) Log.v(TAG, " ++++++++    onPageSelected:  " + i);
            mViewPager.setCurrentItem(i);
            //TODO You can use this position: to write other dependent logic

        }

同时检查:

PagerAdapter中的getItem(int position)

否则,请粘贴您的代码。


3

我注意到,如果您选择不对视图进行动画处理,则在初始创建视图时它可以正常工作。

viewPager2.setCurrentItem(index, false)

根据您的用例,这通常是可以的 - 这个初始/默认项可能不需要动画。


1
这很简单,似乎有些傻。尝试了所有方法来设置第一次创建或重新启动viewpager2活动时的初始默认位置以填充保存的数据。虽然还没有测试完所有方法,但这是一个非常好的答案。谢谢。 - Hmerman6006

1

和 @Daniel Kim 一样,但是是 Java 版本

RecyclerView rvOfViewPager2 = (RecyclerView) viewPager2.getChildAt(0);
    rvOfViewPager2.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
    {
        @Override
        public void onGlobalLayout()
        {
            rvOfViewPager2.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            viewPager2.setCurrentItem(currentTabId, false);
        }
    });

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