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


73

我使用以下代码更改幻灯片:

viewPager.setCurrentItem(index++, true);

但是它变化太快。有没有办法手动设置动画速度?


不确定您的搜索是什么样,但是在SO上查看此答案。那应该正是您所追求的。
MH。

实际上,这与我的解决方案非常相似,尽管我使用的是一个因素而不是绝对的持续时间(因为ViewPager持续时间可能取决于您滚动浏览的页面数)。
Oleg Vaskevich

Answers:


112

我想做自己并已找到解决方案(但是使用反射)。我尚未对其进行测试,但是它应该可以工作或需要最少的修改。在Galaxy Nexus JB 4.2.1上进行了测试。您需要ViewPagerCustomDuration在XML中使用而不是ViewPager,然后可以执行以下操作:

ViewPagerCustomDuration vp = (ViewPagerCustomDuration) findViewById(R.id.myPager);
vp.setScrollDurationFactor(2); // make the animation twice as slow

ViewPagerCustomDuration.java

import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.animation.Interpolator;

import java.lang.reflect.Field;

public class ViewPagerCustomDuration extends ViewPager {

    public ViewPagerCustomDuration(Context context) {
        super(context);
        postInitViewPager();
    }

    public ViewPagerCustomDuration(Context context, AttributeSet attrs) {
        super(context, attrs);
        postInitViewPager();
    }

    private ScrollerCustomDuration mScroller = null;

    /**
     * Override the Scroller instance with our own class so we can change the
     * duration
     */
    private void postInitViewPager() {
        try {
            Field scroller = ViewPager.class.getDeclaredField("mScroller");
            scroller.setAccessible(true);
            Field interpolator = ViewPager.class.getDeclaredField("sInterpolator");
            interpolator.setAccessible(true);

            mScroller = new ScrollerCustomDuration(getContext(),
                    (Interpolator) interpolator.get(null));
            scroller.set(this, mScroller);
        } catch (Exception e) {
        }
    }

    /**
     * Set the factor by which the duration will change
     */
    public void setScrollDurationFactor(double scrollFactor) {
        mScroller.setScrollDurationFactor(scrollFactor);
    }

}

ScrollerCustomDuration.java

import android.annotation.SuppressLint;
import android.content.Context;
import android.view.animation.Interpolator;
import android.widget.Scroller;

public class ScrollerCustomDuration extends Scroller {

    private double mScrollFactor = 1;

    public ScrollerCustomDuration(Context context) {
        super(context);
    }

    public ScrollerCustomDuration(Context context, Interpolator interpolator) {
        super(context, interpolator);
    }

    @SuppressLint("NewApi")
    public ScrollerCustomDuration(Context context, Interpolator interpolator, boolean flywheel) {
        super(context, interpolator, flywheel);
    }

    /**
     * Set the factor by which the duration will change
     */
    public void setScrollDurationFactor(double scrollFactor) {
        mScrollFactor = scrollFactor;
    }

    @Override
    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        super.startScroll(startX, startY, dx, dy, (int) (duration * mScrollFactor));
    }

}

希望这对某人有帮助!


2
您的解决方案是到目前为止我发现的最好的解决方案,但是当行“ scroller.set(this,mScroller);”时 执行后,出现错误“ IllegalArgumentException:字段的值无效”。我猜这是因为使用了ViewPager子类...任何修复?
elgui

另外,如何使用var ViewPagerCustomDuration .mScroller?似乎没有任何关联……
elgui

我会仔细研究一下。应该有可能。另外,mScroller还在内部使用ViewPager
Oleg Vaskevich 2013年

嗯,我测试了这一点对我有用。我不太确定你为什么得到一个IllegalArgumentException
Oleg Vaskevich

1
当启用proguard时,在setScrollDurationFactor this上获得空指针错误
aj0822ArpitJoshi

37

基于@ df778899的答案Android ValueAnimator API,我发现了更好的解决方案。它工作正常,无反射,并且非常灵活。另外,也无需制作自定义ViewPager并将其放入android.support.v4.view包中。这是一个例子:

private void animatePagerTransition(final boolean forward) {

    ValueAnimator animator = ValueAnimator.ofInt(0, viewPager.getWidth());
    animator.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            viewPager.endFakeDrag();
        }

        @Override
        public void onAnimationCancel(Animator animation) {
            viewPager.endFakeDrag();
        }

        @Override
        public void onAnimationRepeat(Animator animation) {
        }
    });

    animator.setInterpolator(new AccelerateInterpolator());
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

        private int oldDragPosition = 0;

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            int dragPosition = (Integer) animation.getAnimatedValue();
            int dragOffset = dragPosition - oldDragPosition;
            oldDragPosition = dragPosition;
            viewPager.fakeDragBy(dragOffset * (forward ? -1 : 1));
        }
    });

    animator.setDuration(AppConstants.PAGER_TRANSITION_DURATION_MS);
    if (viewPager.beginFakeDrag()) {
        animator.start();
    }
}

更新:

只需检查此解决方案是否可用于一次滑动几页(例如,是否应在最后一页之后显示第一页)。这是经过稍微修改的代码,可以处理指定的页数:

private int oldDragPosition = 0;

private void animatePagerTransition(final boolean forward, int pageCount) {
    // if previous animation have not finished we can get exception
    if (pagerAnimation != null) {
        pagerAnimation.cancel();
    }
    pagerAnimation = getPagerTransitionAnimation(forward, pageCount);
    if (viewPager.beginFakeDrag()) {    // checking that started drag correctly
        pagerAnimation.start();
    }
}

private Animator getPagerTransitionAnimation(final boolean forward, int pageCount) {
    ValueAnimator animator = ValueAnimator.ofInt(0, viewPager.getWidth() - 1);
    animator.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            viewPager.endFakeDrag();
        }

        @Override
        public void onAnimationCancel(Animator animation) {
            viewPager.endFakeDrag();
        }

        @Override
        public void onAnimationRepeat(Animator animation) {
            viewPager.endFakeDrag();
            oldDragPosition = 0;
            viewPager.beginFakeDrag();
        }
    });

    animator.setInterpolator(new AccelerateInterpolator());
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            int dragPosition = (Integer) animation.getAnimatedValue();
            int dragOffset = dragPosition - oldDragPosition;
            oldDragPosition = dragPosition;
            viewPager.fakeDragBy(dragOffset * (forward ? -1 : 1));
        }
    });

    animator.setDuration(AppConstants.PAGER_TRANSITION_DURATION_MS / pageCount); // remove divider if you want to make each transition have the same speed as single page transition
    animator.setRepeatCount(pageCount);

    return animator;
}

假装拖拉吗?似乎是实现它的一种聪明方法,但是如果用户在此操作期间开始拖动会发生什么呢?另外,如果我希望它滚动到第一页(由于到达最后一页,我希望能够使其再次到达第一页)怎么办?
Android开发人员

@androiddeveloper,我已将此解决方案与NonSwipeableViewPager(stackoverflow.com/a/9650884/2923816)一起使用。因此,在伪造拖动期间,用户交互没有任何问题。但是,如果有人需要保持滑动手势,可以通过重写ViewPager并在用户触摸ViewPager时控制动画来实现。我没有尝试过,但是我想这是可能的并且应该可以正常工作。对于循环寻呼机,您应该调整动画代码,但是调整取决于循环的具体实现。
lobzik 2015年

关于用户交互,好的(因为动画的速度不是那么慢,所以我不介意)。关于滚动到第一页,我听不懂。假设只有3页,而我位于最后一页,如何使用您设置的自定义持续时间将其滚动到第一页?使用动画师的重复属性会有所帮助吗?
Android开发人员

@androiddeveloper是的,您可以使用重复属性。如果要在与通常的页面过渡相同的时间返回到第一页,则还应该更改此返回动画的过渡持续时间。
lobzik 2015年

我不明白 您能否修改代码以显示如何执行此操作?也许不是“ forward”,而是有一个整数,该整数指示要前进多少页(如果为负,则后退)?
Android开发人员

12
 public class PresentationViewPager extends ViewPager {

    public static final int DEFAULT_SCROLL_DURATION = 250;
    public static final int PRESENTATION_MODE_SCROLL_DURATION = 1000;

    public PresentationViewPager (Context context) {
        super(context);
    }

    public PresentationViewPager (Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setDurationScroll(int millis) {
        try {
            Class<?> viewpager = ViewPager.class;
            Field scroller = viewpager.getDeclaredField("mScroller");
            scroller.setAccessible(true);
            scroller.set(this, new OwnScroller(getContext(), millis));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public class OwnScroller extends Scroller {

        private int durationScrollMillis = 1;

        public OwnScroller(Context context, int durationScroll) {
            super(context, new DecelerateInterpolator());
            this.durationScrollMillis = durationScroll;
        }

        @Override
        public void startScroll(int startX, int startY, int dx, int dy, int duration) {
            super.startScroll(startX, startY, dx, dy, durationScrollMillis);
        }
    }
}

6

更好的解决方案是通过在支持包中创建类来简单地访问私有字段。编辑绑定到MAX_SETTLE_DURATION600ms,由ViewPager类设置。

package android.support.v4.view;

import android.content.Context;
import android.util.AttributeSet;

public class SlowViewPager extends ViewPager {

    // The speed of the scroll used by setCurrentItem()
    private static final int VELOCITY = 200;

    public SlowViewPager(Context context) {
        super(context);
    }

    public SlowViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
        setCurrentItemInternal(item, smoothScroll, always, VELOCITY);
    }

}

当然,您可以添加一个自定义属性,以便可以通过XML进行设置。


谢谢,这似乎确实起作用,但是即使将VELOCITY设置为1,滚动还是非常快的恕我直言,我希望我可以进一步放慢它。
奥利弗·皮尔曼

@HaggleLad是的,很遗憾,我的方法必须限制在600毫秒的最大动画持续时间上。如果需要更长的时间,则需要使用其他方法。我将更新我的答案以注意这一点。
保罗·伯克

8
setCurrentItemInternal不是私有方法吗?
马蒂·米勒

2
@MartyMiller是的,但是如果支持库是项目的一部分,则可以在其包中创建一个类。
保罗·伯克

1
编辑支持库不是一种有效的方式,如果我决定升级它,则此代码将无法正常工作,而且就我个人而言,我没有支持库的源代码(如果有的话,也不会进行编辑)
Ahmed Adel Ismail

0

这是我在Librera Reader中使用的代码

public class MyViewPager extends ViewPager {

  public MyViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        initMyScroller();
    }

    private void initMyScroller() {
        try {
            Class<?> viewpager = ViewPager.class;
            Field scroller = viewpager.getDeclaredField("mScroller");
            scroller.setAccessible(true);
            scroller.set(this, new MyScroller(getContext())); // my liner scroller

            Field mFlingDistance = viewpager.getDeclaredField("mFlingDistance");
            mFlingDistance.setAccessible(true);
            mFlingDistance.set(this, Dips.DP_10);//10 dip

            Field mMinimumVelocity = viewpager.getDeclaredField("mMinimumVelocity");
            mMinimumVelocity.setAccessible(true);
            mMinimumVelocity.set(this, 0); //0 velocity

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

    }

    public class MyScroller extends Scroller {
        public MyScroller(Context context) {
            super(context, new LinearInterpolator()); // my LinearInterpolator
        }

        @Override
        public void startScroll(int startX, int startY, int dx, int dy, int duration) {
            super.startScroll(startX, startY, dx, dy, 175);//175 duration
        }
    }

 }

0

我使用Cicero Moura的版本制作了Kotlin类,该类在Android 10上仍然可以完美运行。

import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.animation.DecelerateInterpolator
import android.widget.Scroller
import androidx.viewpager.widget.ViewPager

class CustomViewPager(context: Context, attrs: AttributeSet) :
        ViewPager(context, attrs) {


    private companion object {
        const val DEFAULT_SPEED = 1000
    }

    init {
        setScrollerSpeed(DEFAULT_SPEED)
    }

    var scrollDuration = DEFAULT_SPEED
        set(millis) {
            setScrollerSpeed(millis)
        }

    private fun setScrollerSpeed(millis: Int) {
        try {
            ViewPager::class.java.getDeclaredField("mScroller")
                    .apply {
                        isAccessible = true
                        set(this@CustomViewPager, OwnScroller(millis))
                    }

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

    inner class OwnScroller(private val durationScrollMillis: Int) : Scroller(context, AccelerateDecelerateInterpolator()) {
        override fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int, duration: Int) {
            super.startScroll(startX, startY, dx, dy, durationScrollMillis)
        }
    }
}

从活动类初始化:

viewPager.apply {
    scrollDuration = 2000
    adapter = pagerAdapter
}

-2

浪费了一整天后,我发现一个解决方案将offscreenPageLimit设置为总数。页面的。

为了保持恒定的长度,ViewPager滚动平滑,setOffScreenLimit(page.length)将所有视图保留在内存中。但是,这对于涉及调用View.requestLayout函数的任何动画(例如,涉及对边距或边界进行更改的任何动画)都构成问题。这使它们真的变慢了(根据Romain Guy的说法),因为内存中的所有视图也会失效。因此,我尝试了几种使事情变得流畅的方法,但是重写requestLayout和其他无效方法会导致很多其他问题。

一个不错的折衷办法是动态修改屏幕外限制,以使页面之间的大多数滚动非常平滑,同时通过在用户删除视图时确保所有页面内动画都平滑。当您只有1个或2个视图而必须将其他视图从内存中删除时,这非常有效。

***在没有任何解决方案起作用时使用此选项,因为通过设置feset limit,u将同时加载所有片段

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.