当以编程方式滑动时,如何更改ViewPager2的滚动速度

27

我知道有一种方法可以通过编程方式更改ViewPager的动画持续时间(这里)。

但是对于ViewPager2,它不起作用。

我尝试过这个:

try {
        final Field scrollerField = ViewPager2.class.getDeclaredField("mScroller");
        scrollerField.setAccessible(true);
        final ResizeViewPagerScroller scroller = new ResizeViewPagerScroller(getContext());
        scrollerField.set(mViewPager, scroller);
    } catch (Exception e) {
        e.printStackTrace();
    }

IDE给我关于"mScroller"的警告:

无法解析字段'mScroller'

如果我们运行这段代码,那么它不会起作用并会给我们以下错误:

No field mScroller in class Landroidx/viewpager2/widget/ViewPager2; (declaration of 'androidx.viewpager2.widget.ViewPager2' appears in /data/app/{packagename}-RWJhF9Gydojax8zFyFwFXg==/base.apk)

那么我们如何实现这个功能呢?


你找到解决方案了吗? - Gary Chen
4个回答

37

根据此问题单,Android团队不打算为ViewPager2支持这种行为,建议使用ViewPager2.fakeDragBy()。该方法的缺点是您需要提供以像素为单位的页面宽度,尽管如果您的页面宽度与ViewPager的宽度相同,则可以使用该值代替。

以下是示例实现:

fun ViewPager2.setCurrentItem(
    item: Int,
    duration: Long,
    interpolator: TimeInterpolator = AccelerateDecelerateInterpolator(),
    pagePxWidth: Int = width // Default value taken from getWidth() from ViewPager2 view
) {
    val pxToDrag: Int = pagePxWidth * (item - currentItem)
    val animator = ValueAnimator.ofInt(0, pxToDrag)
    var previousValue = 0
    animator.addUpdateListener { valueAnimator ->
        val currentValue = valueAnimator.animatedValue as Int
        val currentPxToDrag = (currentValue - previousValue).toFloat()
        fakeDragBy(-currentPxToDrag)
        previousValue = currentValue
    }
    animator.addListener(object : Animator.AnimatorListener {
        override fun onAnimationStart(animation: Animator?) { beginFakeDrag() }
        override fun onAnimationEnd(animation: Animator?) { endFakeDrag() }
        override fun onAnimationCancel(animation: Animator?) { /* Ignored */ }
        override fun onAnimationRepeat(animation: Animator?) { /* Ignored */ }
    })
    animator.interpolator = interpolator
    animator.duration = duration
    animator.start()
}
为了支持RTL,您需要翻转提供给ViewPager2.fakeDragBy()的值,因此在使用RTL时,与上面的示例中的fakeDragBy(-currentPxToDrag)相反,请使用fakeDragBy(currentPxToDrag)
在使用此功能时需要注意以下几点,基于官方文档
  • 负值向前滚动,正值向后滚动(与RTL相反)

  • 在调用fakeDragBy()之前,请使用beginFakeDrag(),完成后请使用endFakeDrag()

  • 此API可以轻松地与ViewPager2.OnPageChangeCallback中的onPageScrollStateChanged一起使用,在那里您可以通过isFakeDragging()方法区分编程拖动和用户拖动
  • 上面的示例实现没有安全检查以确定给定项目是否正确。还要考虑添加取消UI生命周期的功能,这可以通过RxJava轻松实现。

这个例子在我的项目上运行良好,但是由于某种原因,在第一个和最后一个之间的项目在滑动时有些抖动效果,但是最后一个项目只是以正确的方式滑动,这是由于偏移量的像素引起的吗? - Gastón Saillén
你是指jiggle在滚动条的中间吗? - M Tomczynski
当它完成滑动时,在停止之前会晃动一下,晃动发生在滚动的末尾。 - Gastón Saillén
1
@MTomczyński 不用担心,我通过从ViewPager2的实际宽度中减去我应用于内部RecyclerView的左右填充来获得PxWidth正确的页面。 - Alvin Dizon
1
我在使用这种方法后,偶尔会遇到以下错误:“Fatal Exception: java.lang.IllegalStateException Cannot change current item when ViewPager2 is fake dragging”。 - Waqar Vicky
显示剩余15条评论

6
ViewPager2团队让改变滚动速度变得非常困难。如果观察setCurrentItemInternal这个方法,他们会实例化一个私有的ScrollToPosition(..)对象和状态管理代码,所以这将是你必须某种方式覆盖的方法。
从这里获得解决方案:https://issuetracker.google.com/issues/122656759,他们说使用(VIewPager2).fakeDragBy()这非常丑陋。
不是最好的解决方案,只能等待他们给我们一个API来设置持续时间或者复制他们的ViewPager2代码并直接修改他们的LinearLayoutImpl类。

6
他们把问题搁置了,这太荒唐了。 - M Tomczynski
同意。应该有一个滚动速度。因为他们当前的滚动速度很糟糕,特别是当你从一个选项卡切换到另一个选项卡时。 - chitgoks

2
M Tomczynski提供的解决方案(参考:https://dev59.com/lVMH5IYBdhLWcg3w8l0D#59235979)是很好的。但是,当与内置的setCurrentItem()一起使用时,我遇到了应用程序随机崩溃的问题,这是由于虚拟拖动(fakeDrag)引起的,尽管我确保在使用setCurrentItem()之前调用了endDrag()。因此,我修改了解决方案,使用viewpager的嵌入式recycler view的scrollBy()函数,这样做非常好,没有任何问题。
fun ViewPager2.setCurrentItem(
    index: Int,
    duration: Long,
    interpolator: TimeInterpolator = PathInterpolator(0.8f, 0f, 0.35f, 1f),
    pageWidth: Int = width - paddingLeft - paddingRight,
) {
    val pxToDrag: Int = pageWidth * (index - currentItem)
    val animator = ValueAnimator.ofInt(0, pxToDrag)
    var previousValue = 0

    val scrollView = (getChildAt(0) as? RecyclerView)
    animator.addUpdateListener { valueAnimator ->
        val currentValue = valueAnimator.animatedValue as Int
        val currentPxToDrag = (currentValue - previousValue).toFloat()
        scrollView?.scrollBy(currentPxToDrag.toInt(), 0)
        previousValue = currentValue
    }

    animator.addListener(object : Animator.AnimatorListener {
        override fun onAnimationStart(animation: Animator) { }
        override fun onAnimationEnd(animation: Animator) {
            // Fallback to fix minor offset inconsistency while scrolling
            setCurrentItem(index, true)
            post { requestTransform() } // To make sure custom transforms are applied
        }
        override fun onAnimationCancel(animation: Animator) { /* Ignored */ }
        override fun onAnimationRepeat(animation: Animator) { /* Ignored */ }
    })

    animator.interpolator = interpolator
    animator.duration = duration
    animator.start()
}

1
当你想让ViewPager2在某个按钮点击时,按照你的速度、方向和页面数量滚动时,请使用以下函数并传入相应参数。如果你想要从左到右滚动,则将direction设置为true;如果你想要从右到左滚动,则将其设置为false。duration的单位是毫秒,numberOfPages应该为1,除非你想要回到最开始的页面,那么它应该是你的ViewPager中的页面数:
fun fakeDrag(viewPager: ViewPager2, leftToRight: Boolean, duration: Long, numberOfPages: Int) {
    val pxToDrag: Int = viewPager.width
    val animator = ValueAnimator.ofInt(0, pxToDrag)
    var previousValue = 0
    animator.addUpdateListener { valueAnimator ->
        val currentValue = valueAnimator.animatedValue as Int
        var currentPxToDrag: Float = (currentValue - previousValue).toFloat() * numberOfPages
        when {
            leftToRight -> {
                currentPxToDrag *= -1
            }
        }
        viewPager.fakeDragBy(currentPxToDrag)
        previousValue = currentValue
    }
    animator.addListener(object : Animator.AnimatorListener {
        override fun onAnimationStart(animation: Animator?) { viewPager.beginFakeDrag() }
        override fun onAnimationEnd(animation: Animator?) { viewPager.endFakeDrag() }
        override fun onAnimationCancel(animation: Animator?) { /* Ignored */ }
        override fun onAnimationRepeat(animation: Animator?) { /* Ignored */ }
    })
    animator.interpolator = AccelerateDecelerateInterpolator()
    animator.duration = duration
    animator.start()
}

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