ViewPager setCurrentItem(pageId, true) does NOT smoothscroll

Viewed 40673

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?

13 Answers

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

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

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

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