Change ViewPager animation duration when sliding programmatically

AndroidAnimationAndroid Viewpager

Android Problem Overview


I'm changing slide with the following code:

viewPager.setCurrentItem(index++, true);

But it changes too fast. Is there a way to set manually the animation speed?

Android Solutions


Solution 1 - Android

I've wanted to do myself and have achieved a solution (using reflection, however). I haven't tested it yet but it should work or need minimal modification. Tested on Galaxy Nexus JB 4.2.1. You need to use a ViewPagerCustomDuration in your XML instead of ViewPager, and then you can do this:

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));
    }

}

Hope this helps someone!

Solution 2 - Android

I have found better solution, based on @df778899's answer and the Android ValueAnimator API. It works fine without reflection and is very flexible. Also there is no need for making custom ViewPager and putting it into android.support.v4.view package. Here is an example:

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();
    }
}

UPDATE:

Just checked if this solution can be used to swipe several pages at once (for example if first page should be showed after the last one). This is slightly modified code to handle specified page count:

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;
}

Solution 3 - Android

 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);
        }
    }
}

Solution 4 - Android

Better solution is to simply access the private fields by creating the class in the support package. EDIT This is bound to the MAX_SETTLE_DURATION of 600ms, set by the ViewPager class.

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);
    }

}

You can, of course, then add a custom attribute so this can be set via XML.

Solution 5 - Android

Here is my code used in 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
        }
    }

 }

Solution 6 - Android

I used Cicero Moura's version to make a Kotlin class that still works perfectly as of 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)
        }
    }
}

Initializing from the activity class:

viewPager.apply {
    scrollDuration = 2000
    adapter = pagerAdapter
}

Solution 7 - Android

After wasting my whole day I found a solution set offscreenPageLimit to total no. of the page.

In order to keep a constant length ViewPager scrolls smooth, setOffScreenLimit(page.length) will keep all the views in memory. However, this poses a problem for any animations that involves calling View.requestLayout function (e.g. any animation that involves making changes to the margin or bounds). It makes them really slow (as per Romain Guy) because the all of the views that's in memory will be invalidated as well. So I tried a few different ways to make things smooth but overriding requestLayout and other invalidate methods will cause many other problems.

A good compromise is to dynamically modify the off screen limit so that most of the scrolls between pages will be very smooth while making sure that all of the in page animations smooth by removing the views when the user. This works really well when you only have 1 or 2 views that will have to make other views off memory.

***Use this when no any solution works because by setting offeset limit u will load all the fragments at the same time

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionUserView Question on Stackoverflow
Solution 1 - AndroidOleg VaskevichView Answer on Stackoverflow
Solution 2 - AndroidlobzikView Answer on Stackoverflow
Solution 3 - AndroidCícero MouraView Answer on Stackoverflow
Solution 4 - AndroidPaul BurkeView Answer on Stackoverflow
Solution 5 - AndroidFoobnixView Answer on Stackoverflow
Solution 6 - AndroidEugene KartoyevView Answer on Stackoverflow
Solution 7 - AndroidAnil JaiswalView Answer on Stackoverflow