50

I am compiling on SDK 4.03, Samsung Infuse Android 2.2, Support Library for Android 4, and using ViewPager in my app, actual swipe works fine, but when I do

viewPager.setCurrentItem(id); // , or
viewPager.setCurrentItem(id, true);  

It does not smooth scroll, but switches views instantly. Although the documentation clearly states that that is the purpose setting the second argument to true. Whats up with this?

Code Droid
  • 10,344
  • 17
  • 72
  • 112
  • You mean the [support library](http://developer.android.com/tools/extras/support-library.html) by `Android Compatibility Library 4`? – iTurki Aug 15 '12 at 00:25
  • 2
    Are you calling setCurrentItem immediately after calling setAdapter() method? If yes, than you should wait until all views, which view pager is instantiating, are ready. Otherwise it is not working well. – Jan Muller Aug 31 '12 at 13:52
  • 4
    @JanMuller How do you wait until all views are ready? – Flimm Aug 18 '16 at 12:41
  • Check this: http://jerryjobsguo.blogspot.com/2014/06/how-to-change-viewpager-scroll.html – Sumit Shukla May 25 '21 at 05:33

13 Answers13

43

I've fixed this by creating a MyViewPager that overrides the ViewPager.mScroller using reflection.

public class MyViewPager extends ViewPager {

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

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

    public class MyScroller extends Scroller {
        public MyScroller(Context context) {
            super(context, new DecelerateInterpolator());
        }

        @Override
        public void startScroll(int startX, int startY, int dx, int dy, int duration) {
            super.startScroll(startX, startY, dx, dy, 1000 /*1 secs*/);
        }
    }
}
Community
  • 1
  • 1
Marc Van Daele
  • 2,856
  • 1
  • 26
  • 52
16

This is what i did. I overrode the package-private method smoothScrollTo in ViewPager by putting my own custom subclass in the same package. It was being passed a value of zero, which causes the snapping behavior instead of the smooth scroll.

package android.support.v4.view;

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

public class MyViewPager extends ViewPager {

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

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

    void smoothScrollTo(int x, int y, int velocity) {
        super.smoothScrollTo(x, y, 1);
    }
}

It worked great, if you want you can calculate and provide actual velocity ISO of just 1.

Christopher Perry
  • 38,891
  • 43
  • 145
  • 187
tegbird
  • 177
  • 1
  • 3
  • I forgot to mention that if its actual fling than the velocity is non zero, so to differentiate between actual fling and ` viewPager.setCurrentItem(id, true);` use this check. – tegbird Feb 08 '13 at 23:26
  • 2
    There is no method smoothScrollTo(int,int,int) in ViewPager class – heyjii Mar 02 '13 at 12:40
  • 2
    There is! (if you put your MyViewPager in android.support.v4.view) – Marc Van Daele Mar 21 '13 at 13:16
  • This works. ONLY caveat is that you have to put it in the android.support.v4.view package in order to override the package-private method `smoothScrollTo` – Christopher Perry Apr 02 '13 at 22:40
  • 3
    This compiles fine for me but seems to have no effect, the scrolling is still almost instant. – user291701 May 13 '13 at 15:26
  • How exactly do you place your file in the support.v4 package? – Nathan Bird Apr 20 '18 at 21:16
  • @Epicality in the same way you would put your class in any other package. So in this case, instead of putting it in `com/example/app/` you would put it in `android/support/v4/` and at the package line in your class you would write `package android.support.v4;` – Mikael Ohlson Aug 24 '18 at 09:36
16

I called setCurrentItem function in a handler and it worked fine for me.

new Handler().post(new Runnable() {
        @Override
        public void run() {
            myViewPager.setCurrentItem(1, true);
        }
    });

Hope this helps.

ACengiz
  • 1,285
  • 17
  • 23
  • this answer helped me +1 with situation where it wouldn't work only for some layouts – voytez Sep 24 '18 at 21:45
  • 1
    This doesn't seem to work. I'm still seeing the view appear instantly, rather than being scrolled to. – VIN Oct 30 '18 at 15:40
6

I'm aware this thread it pretty old, but this is one of the top Google results. I've been going back and forth on how to solve this problem for quite a bit now. None of the solutions above helped me at all. However, I did find a solution that works for me.

What my setup currently looks like is a listview inside a viewpager. When you click on one of the views it creates a new page and scrolls to it. This was very snappy before, but it seems as though this is because I was calling

mViewPager.setCurrentItem(intIndex, true);

from inside my OnClickEvent. The viewpager doesn't like this for some reason, so instead, I made this function. It creates a new thread that runs a runnable on the UI thread. This runnable is what tells the ViewPager to scroll to a certain item.

public static void scrollToIndex(int index) {

    final int intIndex = index;

    //We're going to make another thread to separate ourselves from whatever
    //thread we are in 
    Thread sepThread = new Thread(new Runnable(){

        public void run()
        {
            //Then, inside that thread, run a runnable on the ui thread.
            //MyActivity.getContext() is a static function that returns the 
            //context of the activity. It's useful in a pinch.
            ((Activity)MyActivity.getContext()).runOnUiThread(new Runnable(){

                @Override
                public void run() {

                    if (mSlidingTabLayout != null)
                    {
                        //I'm using a tabstrip with this as well, make sure
                        //the tabstrip is updated, or else it won't scroll
                        //correctly
                        if(mSlidingTabLayout.getTabStripChildCount() <= intIndex)
                        mSlidingTabLayout.updateTabStrip();

                        //Inside this thread is where you call setCurrentItem
                        mViewPager.setCurrentItem(intIndex, true);

                    }

                }

            });

         }
    });


    sepThread.start();


}

I hope I have at least helped someone with this problem. Good luck!

Edit: Looking over my answer, I'm pretty sure you can just run on the ui thread, and it should work. Take that with a grain of salt though, I haven't tested it yet.

2

I´ve created this class because I wasn't fully satisfied with the above solutions

The class overrides setCurrentItem(int item, boolean smoothScroll) and uses reflection to keep this method as original as possible. The key is that velocity is not 0. You only have to replace your current ViewPager with this MyViewPager class and use it like normally:

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

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import androidx.viewpager.widget.ViewPager;

public class MyViewPager extends ViewPager {

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

    /**
     * Set the currently selected page. If the ViewPager has already been through its first
     * layout with its current adapter there will be a smooth animated transition between
     * the current item and the specified item.
     *
     * @param item         Item index to select
     * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
     */
    @Override
    public void setCurrentItem(int item, boolean smoothScroll) {
        final Class<?> viewpager = ViewPager.class;
        int velocity = 1;

        try {
            Field mPopulatePending = viewpager.getDeclaredField("mPopulatePending");
            mPopulatePending.setAccessible(true);
            mPopulatePending.set(this, false);

            Method setCurrentItemInternal = viewpager.getDeclaredMethod("setCurrentItemInternal", int.class, boolean.class, boolean.class, int.class);
            setCurrentItemInternal.setAccessible(true);
            setCurrentItemInternal.invoke(this, item, smoothScroll, false, velocity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Thomas W.
  • 159
  • 3
  • 4
1

As others later discovered, it is a bug and we have to post it delayed, but it can be quite simple:

pager.post {
    pager.setCurrentItem(1, true)
}

Sadly you have to try first bunch of wrong answers to get to this...

Renetik
  • 5,887
  • 1
  • 47
  • 66
0

The ViewPager was changed a lot from revision 5 to 9. Some of those changes that might be related to your problem:

  • Bug fixes for user interface behavior in ViewPager (revision 5 and 6)
  • Fixed numerous bugs for ViewPager (revision 9)

Giving that your code is supposed to work just fine, my best guess is that your support library is not up-to-date. Try updating the library if it isn't.

iTurki
  • 16,292
  • 20
  • 87
  • 132
  • i still have the same issue with android-support-v4-rev11 – Zsolt Safrany Dec 13 '12 at 16:49
  • You just guess and answer like that and you answered wrongly... It still does not work in latest version to me after years too so obviously there need to be some other solution... – Renetik Jun 22 '20 at 23:12
0

I had the same problem, but today I've found a simple solution. Maybe it will help you. First, lets suppose we have a ViewPager that filled the whole screen. To switch between pages I've created my own custom View with tabs and put it over the ViewPager. Clicking a tab should scroll smoothly to the appropriate page in ViewPager with setCurrentItem(item, true) - but it scrolls instantly, with no smooth! Then I tried to add a simple button (not custom) over the ViewPager + callback:

@Override
public void onClick(View v) {
   viewPager.setCurrentItem(item, true);
}

After that the smooth scroll stared working. So, the solution was very simple: inside the Button class the internal boolean onTouch(...) listener returns true, so it always CONSUMES touch events! Then the smooth scroll started working for my custom tabs view when I substitued "return false" with "return true" in the internal boolean onTouch(...) listener.

I hope my success story can help you.

Andrey Fomenkov
  • 109
  • 1
  • 9
0

For those who use Xamarin (albeit this approach is applicable to Java as well) I can suggest to use the next approach based on the answer above (ViewPager from Android Support Library v7 AppCompat 19.1.0):

public void ScrollSmooth(int i)
{
    var jClass = JNIEnv.GetObjectClass(_pager.Handle);
    var jMethod = JNIEnv.GetMethodID(jClass, "setCurrentItemInternal", "(IZZI)V");
    JNIEnv.CallVoidMethod (_pager.Handle, jMethod, new JValue (i), new JValue (true), new JValue (false), new JValue (1));
}

It relies on ViewPager implementation from http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/support/v4/view/ViewPager.java

Mikhail
  • 568
  • 1
  • 6
  • 22
0

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

please follow this https://stackoverflow.com/a/54800154/9097612

0

Note this variable mFirstLayout - it will be set true while viewpager callback onAttachedToWindow(such as on recyclerview),so will be not smoothScroll. You should override onAttachedToWindow to control the mFirstLayout variable. Something like this:

        @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();
            try {
                Field mFirstLayout = ViewPager.class.getDeclaredField("mFirstLayout");
                mFirstLayout.setAccessible(true);
                mFirstLayout.set(this, false);
                getAdapter().notifyDataSetChanged();
                setCurrentItem(getCurrentItem());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
Zoe
  • 27,060
  • 21
  • 118
  • 148
0

Below code will pause the page for a moment(500 ms) then slide to next.

import androidx.viewpager.widget.ViewPager;


new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            viewPager.setCurrentItem(index, true);
        }
    }, 500);
Kaps
  • 2,345
  • 2
  • 26
  • 37
-6

try with this code:

viewPager.postDelayed(new Runnable()
{
    @Override
    public void run()
    {
        viewPager.setCurrentItem(id, true);
    }
}, 100);
David Figueroa
  • 365
  • 2
  • 6