Looping/infinite ViewPager with page indicator in Android

Recently, I had to implement a looping ViewPager in Android using Fragments. This proved to be a confusing and difficult task because in my head, the only way to do this was to use the ViewPagerIndicator library by Jake Wharton. Turns out, this narrow thinking was my biggest problem.

Ali Muzaffar
7 min readFeb 5, 2016

When I first had to implement a looping ViewPager, my first thought was to just force the ViewPagerAdapter.getCount() method to return Integer.MAX_INT and then under ViewPagerAdapter.getItem() return position%count and…. done! Something like this:

public class SplashScreenPagerAdapter 
extends FragmentPagerAdapter {
...
@Override
public Fragment getItem(int position) {
return mFrags.get(position%mFrags.size());
}
@Override
public int getCount() {
return Integer.MAX_VALUE;
}
}

This immediately came up with three problems:

  1. The application would hang, presumably because the cost of drawing Integer.MAX_VALUE page indicators is too much. So I solved this by temporarily reducing this to 100.
  2. Android threw up an error when a previously attached fragment was going to be reattached. The error basically said that I cannot reattach a fragment with a different tag. I sort of get that, since I'm really just returning references to the same fragments, if a fragment was already on the stack, Android doesn’t want to put an other reference on top with a different tag.
  3. I was using ViewPagerIndicator to show my pages and all of a sudden, I had a row full of dots… Since I was using LinearLayout with orientation horizontal, I only saw a few on the screen, but presumably, they went on till Integer.MAX_INT.

I'm going to go ahead and ignore how much time I had already wasted trying various method to integrate ViewPagerIndicator into my project using gradle dependencies. I tried jitpack, forks people had made etc. and something or the other kept breaking. Finally I gave up and just included the source.

Having put some effort into integrating ViewPagerIndicator, I was going to use it!

Fixing the “Can’t change tag of fragment” error

The error to be exact:

java.lang.IllegalStateException: Can’t change tag of fragment ContentFragment{5283271c #0 id=0x7f0c0054 android:switcher:2131492948:0}: was android:switcher:2131492948:0 now android:switcher:2131492948:3

There are a few ways to work around this.

Create a new fragment every time

Firstly, you can return a new instance of your fragment, instead of returning a reference to previous fragment. So instead of the code snippet above for getItem(int), do something like this:

@Override
public Fragment getItem(int position) {
int index = position%MAX;
switch (index) {
case 0:
return ContentFragment.newInstance("Hello");
case 1:
return ContentFragment.newInstance("World");
case 2:
return ContentFragment.newInstance("!!!!!");
}
return null;
}

Second, if you must use the same fragment, you have to override destroyItem() then remove the fragment and recreate it. If there is a way to make this work, without having to recreate the Fragment, please let me know in the comments. This is not really much better than the first approach.

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
ContentFragment cf = (ContentFragment) object;
mFragMan.beginTransaction().remove(cf).commit();
mFrags.add(position, ContentFragment.newInstance(cf.getmParam1()));
mFrags.remove(position+1);
}

Reuse fragments by using FragmentStatePagerAdapter instead

Lastly, there is an other option to reuse fragments from the array. By converting the FragmentPagerAdapter to a FragmentStatePagerAdapter, you may actually be able to reuse a Fragment. That’s it! you don’t need to override destroyItem() or do anything in it. The only issue is, that this will only work if you have at least 4 fragments. Otherwise you’ll get a “Fragment already active” error. I do briefly talk about how to work around this below.

The reason this works at all, comes down to the difference between FragmentPagerAdapter and FragmentStatePagerAdapter.

This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages

So, in effect, the Fragment is being removed from memory when it is off the screen, hence allowing reuse.

What should I do about ViewPagerIndicator?

As you can see ViewPagerIndicator has gone a little crazy:

There is a lot of literature on the internet about replacing getCount() inside each type of ViewPagerIndicator with getRealCount() and adding a getRealCount() method to your PagerAdapter. This will only solve the problem of showing too many bullets, you also need to change the code, so that when you set the current item, you mod it with the real count. Finally, everywhere you see mCurrentPage or mSnapPage, mod it with real count. This works and you can see the results below. You can even do a diff on my code between CirclePageIndicator and LoopingPageIndicator so see the changes I made. This approach works for CirclePageIndicator and LinePageIndicator, you’ll need to read through the code and modify the other PageIndicators if you need to use them as needed.

Overall, this approach works and is viable since I haven’t changed any code from the original library and I can still use it if I like. If you would like to reuse my code, you need to grab the LoopingPagerAdapter interface and the LoopingCirclePageIndicator. Also, you can now set getCount() in your Adapter to Integer.MAX_INT without any problem since it’s not being used to draw the indicators any more.

Personally, when I went down this route, I realized that the call PageIndicator.setViewPager(ViewPager) is not part of the Android framework. PageIndicator is an interface which is part of the ViewPagerIndicator library. If you knew this already… you’re better than me. ViewPagerIndicator has been around so long and so synonymous with Android development that you can’t fault someone for thinking that the PageIndicator interface is part of the Android API.

Note: If you have fewer than 4 fragments, and want to use FragmentStatePagerAdapter approach, you can probably get away
with hardcoding getRealCount() to 2 or 3 and creating duplicates of all the fragments.

Do it yourself!

What I learned from digging through ViewPagerIndicators code is that they are all basically just CustomViews that attach a OnPageChangeListener to your ViewPager and just draw the components. Rather than go through all the business above, I could have just implement something like this myself in a fraction of the time. As a matter of fact, that’s exactly what I did, and you can see the results in the video below.

Instead of drawing the circles or lines or squares myself in a custom View, I can just use a Drawable. I created a drawable with a default state and one with a selected state.

<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<shape android:shape="oval">
<solid android:color="@android:color/white" />
<stroke android:width="1dp" android:color="@android:color/white" />
</shape>
</item>
<item>
<shape android:shape="oval">
<stroke android:width="1dp" android:color="@android:color/white" />
</shape>
</item>
</selector>

Depending on the indicators I want to show, just add Views to my layout with this set as the background resource. Set a View as selected to show the selected state.

Doing it yourself has a few great benefits.

  • You can show progress, as steps of a sign up page, show the pages that have already filled with a selected state and all the others with the unselected state.
  • You can literally position them anywhere, you can even have them scattered on different parts of the page if you like.
  • You don’t have to use them with a ViewPager. Since you wrote the code yourself, you use them whatever you like!

You can take a look at a sample implementation I did here. Keep in mind that it’s a quick implementation.

Note: If you have fewer than 4 fragments, and want to use FragmentStatePagerAdapter approach, you can probably get away
with passing to 2 or 3 for the page count and creating duplicates of all the fragments.

Investigating options: InfiniteViewPager

When I first got stuck, my mind immediately thought “this must be a common problem. Others must have already solved it.” And surely there were a few. The only one that I felt worked at all and should be mentioned was InfiniteViewPager. I download the source, built the demo app and it seemed to work great. Except for one thing. There was no page indication. I went ahead and tried it anyway with ViewPagerIndicator.

What I found was that after the first set of page of scrolls, ViewPagerIndicator effectively would stop working. The reason for this is that InfiniteViewPager works by manipulating the underlying array. It constantly moves items from the front of the array to the back and vice versa as you move. This understandably is confusing for ViewPager. Even worst, this can result on your onPageSelected method being called twice, once by the InfiniteViewPagerAdapter and once by ViewPagerIndicator. Also, the position is not reliably correct, so trying to use position in onPageSelected to show any indication is not going to work.

There is a project called InfinitePageIndicator that sought to resolve this issue. If including this project into your code is viable for you, then great! In my case, I wanted to have an unmodifed instance of ViewPagerIndicator and I had other requirements which made the InfiniteViewPager impractical.

Finally

You can take a look at the demo app source I built which should showcase the various approaches I talked about. I hope this has been helpful.

In order to build great Android apps, read more of my articles.

Yay! you made it to the end! We should hang out! feel free to follow me on Medium, LinkedIn, Google+ or Twitter.

--

--

Ali Muzaffar

A software engineer, an Android, and a ray of hope for your darkest code. Residing in Sydney.