23 August 2011

Horizontal View Swiping with ViewPager, Updated

Posted by Rich “geekyouup” Hyndman, inspired by the fact that life just got that little bit easier

Updated Dec 12 2012

It has been over a year since the ViewPager launched and in that time it’s been used by many developers to improve the user experience of their applications. Over the year the APIs have been updated, adding some new features and deprecating some old method signatures.

Five methods in PagerAdapter have been deprecated in order to switch from using View in the method signature to using ViewGroup. This makes it more obvious what the parameter refers to, improves the readability of the code and removes the need to constantly cast the Views to ViewGroups.

Some official ViewPager resources have also been added:

  • The ViewPager support library classes have been included into our API Docs.
  • A ViewPager lesson has been added to Android Training.
  • Some great design hints for Swipe Views are available in the Android Design Guidelines, explaining how to deal with pages that can scroll horizontally.

A few ViewPager hints that are worth mentioning as they keep recurring are:

  • Think about modifying the number of pages that are cached. This is especially important when you only have 3 or 4 pages. The default setting will store 1 page either side of the current page. In the scenario that you have 3 pages, swiping to the middle page will mean that all of your pages will be cached. Then swiping to the first or last page will drop one of the pages out of memory and they will need to be recreated and re-added when you swipe back again. By setting setOffscreenPageLimit(2) you’ll allow all of your pages to stay in memory all the time. This is a trade off between performance and memory considerations, so it is a good idea to listen for low memory warnings and be prepared to remove edge pages if necessary.
  • If you’re trying to replace Views in your ViewPager, it isn’t enough just to change the data set behind the adapter and call notifyDataSetChanged(). You also need to ensure that you’ve correctly implemented getItemPosition(Object object) and return POSITION_NONE for items that have changed and return POSITION_UNCHANGED or the actual position for items that haven’t changed.
  • Another API that was added is setPageMargin() and setPageMarginDrawable(), allowing you to easily separate your pages.

Here’s to the next year of paging views. The updated original post follows...


Whether you have just started out in Android app development or are a veteran of the craft, it probably won’t be too long before you’ll need to implement horizontally scrolling sets of views.
Many existing Android apps already use this UI pattern. ViewPager standardizes the implementation.

ViewPager was released as part of the Compatibility Package revision 3 and works with Android 1.6 upwards. After following the instructions to obtain the package you can right-click on your Android project in Eclipse, choose ‘Android Tools’ and ‘Add Compatibility Library’, making the new classes available.

ViewPager is a ViewGroup and works in a similar manner to AdapterViews (like ListView and Gallery) so it shouldn’t feel too foreign. Note that if you use ViewPager in an xml layout, be sure to use the full class reference, e.g.

 <android.support.v4.view.ViewPager
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        … />

ViewPagers source their views from PagerAdapters which give you have full control over the reuse and recycling of the views. A PagerAdapter implementation called FragmentPagerAdapter is provided to facilitate the use of Fragments in a ViewPager; This is immensely powerful and as simple as implementing getCount() and getItem(). There is a sample called Fragment Pager Support provided in the Support Demos to illustrate this.

    public static class MyAdapter extends FragmentPagerAdapter {
        public MyAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public int getCount() {
            return NUM_ITEMS;
        }

        @Override
        public Fragment getItem(int position) {
            return ArrayListFragment.newInstance(position);
        }
    }

FragmentPagerAdapter will detach each fragment as you swipe through the list, but keep them in memory so they can simply be reattached when the user swipes back. If you have a larger number of Fragments, the FragmentStatePagerAdapter is worth considering as it will remove them, with the downside being they need to be rebuilt as the user swipes back to them. So, if you have fewer, more complex fragments the FragmentPagerAdapter makes sense, but consider FragmentStatePagerAdapter for larger sets.

On the more simplistic side I recently wrote a ViewPager/PagerAdapter example that serves up simple TextViews. One thing to note is that if you are implementing your own PagerAdapter it is up to you, the developer, to add and remove your views to and from the ViewGroup. To facilitate this the ViewPager is passed into the PagerAdapter methods instantiateItem() and destroyItem().

    @Override
    public Object instantiateItem(ViewGroup collection, int position) {
        View v = layoutInflater.inflate(...);
        ...
        ((ViewPager) collection).addView(v,0);
        return v;
    }

    @Override
    public void destroyItem(ViewGroup collection, int position, Object view) {
        ((ViewPager) collection).removeView((TextView) view);
    }

The source code for ViewPager is also included and available in <android-sdk>/extras/android/compatibility/v4/src. It is worth checking as you can generate the reference documentation from it using Javadoc. In the reference docs / source you’ll find other useful methods, for example setOnPageChangeListener(), which enables your application to track which View is currently visible.