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

Posted

技术标签:

【中文标题】以编程方式滑动时更改 ViewPager2 滚动速度【英文标题】:Change ViewPager2 Scroll Speed when sliding programmatically 【发布时间】:2019-12-21 16:06:10 【问题描述】:

我知道有办法改变 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)

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

【问题讨论】:

找到解决方案了吗? 【参考方案1】:

基于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)

基于official docs:

负值向前滚动,正值向后滚动(用 RTL 翻转)

在调用fakeDragBy() 之前使用beginFakeDrag() 并在完成之后使用endFakeDrag()

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

【讨论】:

这个例子适用于我的项目,但是,由于某种原因,第一个和最后一个之间的项目在滑动时会有一些傻笑效果,但最后一个项目只是滑入正确的方式,这是造成的吗按偏移量的 px? @GastónSaillén 此行为表明您可能使用了错误的页面宽度值(以像素为单位)。抖动是由于过度滚动还是滚动不够远? 或者你的意思是抖动在滚动的中间? 滑动结束时会晃动一下,最后会停一点,晃动在滚动的末尾 @MTomczyński 不用担心,我通过从实际的 ViewPager2 宽度(以像素为单位)中减去应用于 ViewPager2 内部 recyclerview 的左右填充来得出正确的 pagePxWidth。【参考方案2】:

ViewPager2 团队很难改变滚动速度。如果您查看 setCurrentItemInternal 方法,它们会实例化自己的私有 ScrollToPosition(..) 对象。连同状态管理代码,所以这将是您必须以某种方式覆盖的方法。

作为解决方案:https://issuetracker.google.com/issues/122656759,他们说使用(ViewPager2).fakeDragBy(),这非常丑陋。

不是最好的,只需要等待他们给我们一个 API 来设置持续时间或复制他们的 ViewPager2 代码并直接修改他们的 LinearLayoutImpl 类。

【讨论】:

他们把这个问题冰封了,这太荒谬了 同意。应该有滚动速度。因为它们当前的滚动速度很糟糕,尤其是当您从一个标签切换到下一个时。【参考方案3】:

当您希望 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()

【讨论】:

以上是关于以编程方式滑动时更改 ViewPager2 滚动速度的主要内容,如果未能解决你的问题,请参考以下文章

以编程方式滑动时更改ViewPager动画持续时间

使用Android ViewPager2在回收器视图中滚动到底部时自动更改选项卡项

ViewPager2:以编程方式选择特定片段

UIView(放置在滚动视图内)在以编程方式更改高度常量时不显示其子视图

以编程方式更改 Toolbar 和 CollapsingToolbarLayout 滚动标志

UIScrollView 不使用自动布局滚动(内容视图框架大小以编程方式更改)