Code Droid
Code Droid

Reputation: 10472

ViewPager setCurrentItem(pageId, true) does NOT smoothscroll

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?

Upvotes: 52

Views: 42826

Answers (14)

Plaban Das
Plaban Das

Reputation: 53

This solution is working for me:

viewPager.setOffscreenPageLimit(3);

It adjust this value based on your number of pages. For example You have 4 pages then you can use 4 instead of 3

Upvotes: 0

Renetik
Renetik

Reputation: 6373

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

Upvotes: 1

Kaps
Kaps

Reputation: 2365

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

Upvotes: 0

张群峰
张群峰

Reputation: 1

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

Upvotes: 0

Anil Jaiswal
Anil Jaiswal

Reputation: 11

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

Upvotes: 0

Thomas W.
Thomas W.

Reputation: 349

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

Upvotes: 2

David Figueroa
David Figueroa

Reputation: 385

try with this code:

viewPager.postDelayed(new Runnable()
{
    @Override
    public void run()
    {
        viewPager.setCurrentItem(id, true);
    }
}, 100);

Upvotes: -6

Grant Oberhauser
Grant Oberhauser

Reputation: 76

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.

Upvotes: 6

ACengiz
ACengiz

Reputation: 1315

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.

Upvotes: 17

Marc Van Daele
Marc Van Daele

Reputation: 2734

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

Upvotes: 43

Mikhail
Mikhail

Reputation: 608

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

Upvotes: 0

tegbird
tegbird

Reputation: 177

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.

Upvotes: 16

Andrey Fomenkov
Andrey Fomenkov

Reputation: 109

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.

Upvotes: 0

iTurki
iTurki

Reputation: 16398

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.

Upvotes: 0

Related Questions