ViewPager as a circular queue / wrapping

I am using a ViewPager with the FragmentStatePagerAdapter to allow navigation between some fragments.

Let's say I have three fragments: A, B and C. The ViewPager shows Fragment A initially, and allows you to navigate to Fragment B by swiping from right-to-left, and then to Fragment C by swiping again. This allows the following navigation paths: A <--> B <--> C.

What I would like is to be able to swipe from left-to-right on Fragment A and have the ViewPager show Fragment C, i.e. for it to behave as a circular queue and allow ... --> C <--> A <--> B <--> C <--> A <-- ...

I do not want the Fragments duplicated in other positions (i.e. ending up with more than three instances).

Is this wrapping functionality possible with a ViewPager?


Solution 1:

I've implemented a ViewPager/PagerAdapter that can allow for pseudo-infinite paging behaviour. It works by specifying a very large number as the actual count, but maps them to the actual range of the dataset/pageset. It offsets the beginning by a large number also so that you can immediately scroll to the left from the 'first' page.

It doesn't work so well once you get to 1,000,000th page (you will see graphical glitches when scrolling), but this is typically not a real-world use-case. I could fix this by resetting the count to a lower number once in a while, but I will leave it how it is for now.

The InfinitePagerAdapter wraps an existing ViewPager, and so the usage is quite transparent. The InfiniteViewPager does does a bit of work to make sure you can potentially scroll to the left and right many times.

https://github.com/antonyt/InfiniteViewPager

Solution 2:

This is a solution without fake pages and works like a charm:

public class CircularViewPagerHandler implements ViewPager.OnPageChangeListener {
    private ViewPager   mViewPager;
    private int         mCurrentPosition;
    private int         mScrollState;

    public CircularViewPagerHandler(final ViewPager viewPager) {
        mViewPager = viewPager;
    }

    @Override
    public void onPageSelected(final int position) {
        mCurrentPosition = position;
    }

    @Override
    public void onPageScrollStateChanged(final int state) {
        handleScrollState(state);
        mScrollState = state;
    }

    private void handleScrollState(final int state) {
        if (state == ViewPager.SCROLL_STATE_IDLE) {
            setNextItemIfNeeded();
        }
    }

    private void setNextItemIfNeeded() {
        if (!isScrollStateSettling()) {
            handleSetNextItem();
        }
    }

    private boolean isScrollStateSettling() {
        return mScrollState == ViewPager.SCROLL_STATE_SETTLING;
    }

    private void handleSetNextItem() {
        final int lastPosition = mViewPager.getAdapter().getCount() - 1;
        if(mCurrentPosition == 0) {
            mViewPager.setCurrentItem(lastPosition, false);
        } else if(mCurrentPosition == lastPosition) {
            mViewPager.setCurrentItem(0, false);
        }
    }

    @Override
    public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
    }
}

You just have to set it to your ViewPager as onPageChangeListener and that's it: ( ** deprecated now ** check the edit notes)

viewPager.setOnPageChangeListener(new CircularViewPagerHandler(viewPager));

To avoid having this blue shine at the "end" of your ViewPager you should apply this line to your xml where the ViewPager is placed:

android:overScrollMode="never"

We have improved the solution above and created a little library on github. Feel free to check it out :)

Edit:: As per the @Bhavana comment , Just use addOnPageChangeListener instead of setOnPageChangeListener as later is deprecated.

 viewPager.addOnPageChangeListener(new CircularViewPagerHandler(viewPager));