76

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?

2
  • Not sure what your search looked like, but take a look at this answer here on SO. That should be precisely what you're after. Commented May 30, 2012 at 7:55
  • Actually that's quite similar to my solution, though I used a factor instead of absolute duration (because in a ViewPager the duration may depend on how many pages you scroll through). Commented Jan 6, 2013 at 5:58

7 Answers 7

115

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)); } } 
Sign up to request clarification or add additional context in comments.

14 Comments

your solution is the nicest I've found so far, but when the line "scroller.set(this, mScroller);" is executed, I got error "IllegalArgumentException: invalid value for field". I guess this is because of the use of a ViewPager subclass... any fix ?
also, how the var ViewPagerCustomDuration .mScroller can be used ? it appears not to be linked to anything...
I'll look into it in a bit. It should be possible. Also mScroller is just used internally by ViewPager.
Hmm I tested this to work for me; I'm not too sure why you're getting an IllegalArgumentException.
getting null pointer error on setScrollDurationFactor this , when proguard is enable android
|
42

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

13 Comments

so it does fake dragging? seems like a clever way to achieve it, but what happens if the user starts dragging during this operation? Also, what if I want it to scroll to the first page (because when it reaches the last page, I'd like to be able to make it reach the first page again) ?
@androiddeveloper, I've used this solution with NonSwipeableViewPager (stackoverflow.com/a/9650884/2923816). So I have no issues with user interaction during fake dragging. But if someone needs to keep swipe gesture, it could be achieved by overriding ViewPager and controlling the animation when the user touches the ViewPager. I didn't try it, but I suppose it is possible and should work fine. For a looping pager you should adjust the animation code, but the adjustments depend on the concrete implementation of looping.
About the user interaction, ok (since the animation isn't that slow, I don't mind). About scrolling to the first page, I don't understand. Suppose there are just 3 pages, and I'm on the last one, how can I make it scroll to the first page using the customized duration you've set? would using the repeating property of the animator help?
@androiddeveloper yes you can use repeating property. In case when you want to get back to the first page by the same time as usual page transition gets, you should also change transition duration for this returning animation.
I don't understand. Can you please modify the code to show how to do this? Maybe instead of "forward" have an integer that says how many pages to go (and if negative, go backwards) ?
|
14
 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); } } } 

Comments

6

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 ViewPagerclass.

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.

9 Comments

Thanks, this does seem to work however even with VELOCITY set to 1 the scroll is still pretty fast IMHO, I wish I could slow it further.
@HaggleLad Yeah, unfortunately, my approach is bound to the max animation duration of 600 ms. If you need it to be longer than that, you'll need to use a different method. I will update my answer to note this.
isn't setCurrentItemInternal a private method?
@MartyMiller Yes, but if the support lib is part of your project, you can create a class in it's package.
editing support library is not a valid manner, if i decided to upgrade it, this code wont work, and also in my personal case, i dont have the source code for support library (and wont edit it if i have)
|
0

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

Comments

0

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 } 

Comments

-1

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.