Saturday, December 1, 2012

Enable infinite paging with Android ViewPager

UPDATE

I wrote an infinite ViewPager library that you can include in your project.
--------

Hi,

It's been a while since I blogged about something. This post is about enabling infinite paging with the Android ViewPager View. The question on how to do this was initially a question of mine on stackoverflow. Since some people asked me to publish some code, I thought I'd rather blog about this.

Note that this way has been worked for me. If you have any other way to do that, I'd be happy if you would share it.

In the stackoverflow thread I stated [1], that it is necessary to import the source files of the android v4 support library, because (at that time) there had to be a slight adjustment in the ViewPager Method. Now I found out that this is not necessary anymore (the adjustment was already there when I looked into the code. Looks like the android developers changed it).

So, all you need is to include the android support library to your project.
The result will look like this:

Page "-15"

Page "0"












Now let's get to the coding part. First of, we create a layout xml, which includes the ViewPager View.

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</FrameLayout>

Then we create another xml for the page content. Let's call it content.xml.


<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/textview"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</TextView>

For each page, we need a model class that holds the content of each page. We name the class PageModel. Note that we are also holding the view of the page (the TextView) that will be displayed to the user. This enables us to do the background content manipulation.

public class PageModel {

 private int index;
 private String text;
 public TextView textView;

 
 public PageModel(int index) {
  this.index = index;
  setIndex(index);
 }

 public int getIndex() {
  return index;
 }

 public void setIndex(int index) {
  this.index = index;
  setText(index);
 }

 public String getText() {
  return text;
 }

 private void setText(int index) {
  this.text = String.format("Page %s", index);
 }
}

Now we prepared the model for the paging. The following class manages the pages and the background content manipulation and switching the pages in a way that the user thinks that there are infinite pages.

Now we create an avitiy calles MainActivity:

public class MainActivity extends Activity {

 
 // we name the left, middle and right page
 private static final int PAGE_LEFT = 0;
 private static final int PAGE_MIDDLE = 1;
 private static final int PAGE_RIGHT = 2; 
 
 
 private LayoutInflater mInflater;
 private int mSelectedPageIndex = 1;
// we save each page in a model
 private PageModel[] mPageModel = new PageModel[3];
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  // initializing the model
  initPageModel();
  
  mInflater = getLayoutInflater();
  MyagerAdaper adapter = new MyagerAdaper();
    
  final ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
  viewPager.setAdapter(adapter);
  // we dont want any smoothscroll. This enables us to switch the page
  // without the user notifiying this
  viewPager.setCurrentItem(PAGE_MIDDLE, false);
  
  viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
   
   @Override
   public void onPageSelected(int position) {
    mSelectedPageIndex = position;
   }
   
   @Override
   public void onPageScrolled(int arg0, float arg1, int arg2) { }
   
   @Override
   public void onPageScrollStateChanged(int state) {
    if (state == ViewPager.SCROLL_STATE_IDLE) {
     
     final PageModel leftPage = mPageModel[PAGE_LEFT];
     final PageModel middlePage = mPageModel[PAGE_MIDDLE];
     final PageModel rightPage = mPageModel[PAGE_RIGHT];
     
     final int oldLeftIndex = leftPage.getIndex();
     final int oldMiddleIndex = middlePage.getIndex();
     final int oldRightIndex = rightPage.getIndex();
     
     // user swiped to right direction --> left page
     if (mSelectedPageIndex == PAGE_LEFT) {
       
      // moving each page content one page to the right
      leftPage.setIndex(oldLeftIndex - 1);
      middlePage.setIndex(oldLeftIndex);
      rightPage.setIndex(oldMiddleIndex);
      
      setContent(PAGE_RIGHT);
      setContent(PAGE_MIDDLE);
      setContent(PAGE_LEFT);
      
     // user swiped to left direction --> right page
     } else if (mSelectedPageIndex == PAGE_RIGHT) {

      leftPage.setIndex(oldMiddleIndex);      
      middlePage.setIndex(oldRightIndex);
      rightPage.setIndex(oldRightIndex + 1);

      setContent(PAGE_LEFT);
      setContent(PAGE_MIDDLE);
      setContent(PAGE_RIGHT);
     }
     viewPager.setCurrentItem(PAGE_MIDDLE, false);
    }
   }
  });
 }
 
 private void setContent(int index) {
  final PageModel model =  mPageModel[index];
  model.textView.setText(model.getText());
 }

 private void initPageModel() {
  for (int i = 0; i < mPageModel.length; i++) {
   // initing the pagemodel with indexes of -1, 0 and 1
   mPageModel[i] = new PageModel(i - 1);
  }  
 }

 private class MyagerAdaper extends PagerAdapter {

  @Override
  public int getItemPosition(Object object) {
   return POSITION_NONE;
  }

  @Override
  public void destroyItem(ViewGroup container, int position, Object object) {
   container.removeView((View) object);
  }

  @Override
  public int getCount() {
   // we only need three pages
   return 3;
  }

  @Override
  public Object instantiateItem(ViewGroup container, int position) {
   TextView textView = (TextView)mInflater.inflate(R.layout.content, null);
   PageModel currentPage = mPageModel[position];
   currentPage.textView = textView;
   textView.setText(currentPage.getText());
   container.addView(textView);
   return textView;
  }

  @Override
  public boolean isViewFromObject(View view, Object obj) {
   return view == obj;
  }
 }
The "magic" for infinite paging happens at the onPageScrollStateChanged event. Each time when a page change occurs, we move the content to the right (or left depending on the page the user changed  to), change the content of each page (with the method setContent()) and switch again to the middle page. This enables the user to change the page (either to the left or right) again.

Of course the background content manipulation has been kept very simple. Moving more complex content from one page to another requires moving the childviews of a ViewGroup.

Hope it helped. And of course, the code can be downloaded here.

Happy coding

References
[1] Original stackoverflow question - http://stackoverflow.com/questions/7766630/changing-viewpager-to-enable-infinite-page-scrolling

Inspiring app that helped me writing the solution http://code.google.com/p/electricsleep/source/browse/trunk/src/com/androsz/electricsleepbeta/app/HistoryMonthActivity.java