What internet tells us…

It looks like that the most popular answer on the internet for implementing a looping/infinite ViewPager in Android is as shown below.

public class LoopingViewPagerAdapter extends PagerAdapter {

    ......

    @Override
    public int getCount() {
        return Integer.MAX_VALUE;
    }

    ......

}

I was like :thinking: when I first saw this approach. Anyway, after some research I finally found a correct way to implement this thing.

Correct Approach

The idea is quite simple. Let’s assume we have three pages A, B, C. The way we want to put them in the ViewPager is like this:

[C][A][B][C][A]

  • We start with position 1.
  • When hit position 0 -> jump to position 3.
  • When hit position 4 -> jump to position 1.

Let’s Get to Codes

/* Adapter */
public class LoopingViewPagerAdapter extends PagerAdapter {

    private ArrayList<Object> models;

    ......

    @Override
    public int getCount() {
        // Since we want to put 2 additional pages at left & right,
        // the actual size will plus 2.
        return models.size() == 0 ? 0 : models.size() + 2;
    }

    public int getRealCount() {
        return models.size();
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        int modelPosition = mapPagerPositionToModelPosition(position);

        Object model = models.get(modelPosition);

        return // create view using the model.
    }

    private int mapPagerPositionToModelPosition(int pagerPosition) {
        // Put last page model to the first position.
        if (pagerPosition == 0) {
            return getRealCount() - 1;
        }
        // Put first page model to the last position.
        if (pagerPosition == getRealCount() + 1) {
            return 0;
        }
        return pagerPosition - 1;
    }

}
/* Listener */
listener = new ViewPager.OnPageChangeListener() {

    private int jumpPosition = -1;

    @Override
    public void onPageScrolled(int position,
                               float positionOffset,
                               int positionOffsetPixels) {
        // We do nothing here.
    }

    @Override
    public void onPageSelected(int position) {
        if (position == 0) {
            // prepare to jump to the last page
            jumpPosition = adapter.getRealCount();

            //TODO: indicator.setActive(adapter.getRealCount() - 1)
        } else if (position == adapter.getRealCount() + 1) {
            //prepare to jump to the first page
            jumpPosition = 1;

            //TODO: indicator.setActive(0)
        } else {
            //TODO: indicator.setActive(position - 1)
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        //Let's wait for the animation to complete then do the jump.
        if (jumpPosition >= 0 
                && state == ViewPager.SCROLL_STATE_IDLE) {
            // Jump without animation so the user is not 
            // aware what happened.
            viewPager.setCurrentItem(jumpPosition, false);
            // Reset jump position.
            jumpPosition = -1;
        }
    }
};

NOTE: Due to the way ViewPager is implemented, there’s no way you can completely swipe to the next page. So when touch up, the ViewPager will determine the next page according to your scroll position and animate to it. The bad part about this is that, onPageSelected is hit when the animation starts. So if you do the jump here you will cancel scroll animation and it will look weird. The better solution is to wait for the animation to complete and then do the jump.

/* Putting things together */
viewPager = (ViewPager) findViewById(R.id.view_pager_id);
viewPager.addOnPageChangeListener(listener);

adapter = new LoopingViewPagerAdapter(models);

viewPager.setAdapter(adapter);
viewPager.setCurrentItem(1, false);

Now we are done. Nice and easy. Let’s get a beer. :beer: