android 以编程方式滑动时更改ViewPager2滚动速度

vyswwuz2  于 2023-06-28  发布在  Android
关注(0)|答案(4)|浏览(747)

我知道有办法改变动画持续时间的ViewPager编程幻灯片(here)。
但它在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)

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

4bbkushb

4bbkushb1#

基于this issue ticket,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轻松实现。
6vl6ewon

6vl6ewon2#

ViewPager2团队很难改变滚动速度。如果您查看方法setCurrentItemInternal,它们示例化了自己的私有ScrollToPosition(..)对象。沿着状态管理代码,所以这将是您必须以某种方式重写的方法。
作为一个解决方案,从这里:https://issuetracker.google.com/issues/122656759,他们说使用(ViewPager2).fakeDragBy(),这是超级丑陋的。
不是最好的,只需要等待他们给予我们一个API来设置持续时间或复制他们的ViewPager2代码并直接修改他们的LinearLayoutImpl类。

deyfvvtc

deyfvvtc3#

当你想让ViewPager2按照你的速度、方向和页数滚动时,点击一些按钮,用你的参数调用这个函数。Direction可以是leftToRight = true,如果你想这样做,或者false,如果你想从右到左,持续时间以毫秒为单位,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()
}
zynd9foi

zynd9foi4#

M Tomczynski给出的解决方案(参考:https://stackoverflow.com/a/59235979/7608625)是一个很好的。但是,当与内置的setCurrentItem()沿着使用时,由于fakeDrag,我面临应用程序随机崩溃,即使我确保在使用setCurrentItem()之前调用endDrag()。所以我修改了解决方案,使用viewpager的嵌入式回收器视图的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()
}

相关问题