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

问题描述 投票:0回答:5

我知道有办法改变

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)

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

android androidx android-viewpager2
5个回答
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()
的值,因此在使用
fakeDragBy(-currentPxToDrag)
时,使用
fakeDragBy(currentPxToDrag)
而不是
RTL

根据官方文档,使用此功能时需要记住一些事情:

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

  • 在致电

    fakeDragBy()
    之前使用
    beginFakeDrag()
    以及在完成后
    endFakeDrag()

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

6
投票

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

作为这里的解决方案:https://issuetracker.google.com/issues/122656759,他们说使用

(ViewPager2).fakeDragBy()
,这是超级丑陋的。

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


2
投票

M Tomczynski 给出的解决方案(参考:https://stackoverflow.com/a/59235979/7608625)是一个很好的解决方案。但由于 fakeDrag,当与内置 setCurrentItem() 一起使用时,我面临应用程序随机崩溃,即使我确保在使用 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()
}

1
投票

当您希望 ViewPager2 以您的速度、方向和页数滚动时,在单击某个按钮时,请使用您的参数调用此函数。如果你想要从右到左,方向可以是 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()
}

0
投票

通过扩展PagerSnapHelper并重写createSnapScroller函数来制作CustomPagerSnapHelper

下面是相同的代码。 注意:在我的例子中,我只是将时间乘以 2 倍,这会导致动画速度变慢。根据您的需要更改时间变量。我建议使用多个来进一步减慢动画速度,而不是硬编码值

public class CustomPagerSnapHelper extends PagerSnapHelper {

    RecyclerView mRecyclerView;
    static final float MILLISECONDS_PER_INCH = 100f;
    private static final int MAX_SCROLL_ON_FLING_DURATION = 100; // ms

    CustomPagerSnapHelper(RecyclerView mRecyclerView) {
        this.mRecyclerView = mRecyclerView;
    }

    @Override
    protected LinearSmoothScroller createSnapScroller(RecyclerView.LayoutManager layoutManager) {
        if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
            return null;
        }
        return new LinearSmoothScroller(mRecyclerView.getContext()) {
            @Override
            protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
                int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(),
                        targetView);
                final int dx = snapDistances[0];
                final int dy = snapDistances[1];
                final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
                if (time > 0) {
                    action.update(dx, dy, time*2/* time represent the duration of scroll animation */, mDecelerateInterpolator);
                }
            }

            @Override
            protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
                return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
            }

            @Override
            protected int calculateTimeForScrolling(int dx) {
                return Math.min(MAX_SCROLL_ON_FLING_DURATION, super.calculateTimeForScrolling(dx));
            }
        };
    }
}

在将适配器注入 viewpager 之前,请使用以下代码..

viewPager = findViewById(R.id.viewPagerMain);
        
        try {
            Field mScroller = ViewPager2.class.getDeclaredField("mRecyclerView");
            mScroller.setAccessible(true);
            RecyclerView recyclerView = (RecyclerView) mScroller.get(viewPager);

            Field pagerSnap = ViewPager2.class.getDeclaredField("mPagerSnapHelper");
            pagerSnap.setAccessible(true);
            CustomPagerSnapHelper customPagerSnapHelper = new CustomPagerSnapHelper(recyclerView);
            pagerSnap.set(viewPager,customPagerSnapHelper);
            recyclerView.setOnFlingListener(null);
            customPagerSnapHelper.attachToRecyclerView(recyclerView);


        } catch (Exception e) {
            e.printStackTrace();
        }

        viewPager.setAdapter(new RecyclerViewAdapter(this, images));
© www.soinside.com 2019 - 2024. All rights reserved.