ViewPager Basics

September 13, 2016 0 Comments Getting Started, Android

Let's learn how to build a great ViewPager leveraging the support-v4 library for the Pager Indicator.
But what's a ViewPager? A ViewPager is a View letting the user swiping left and right to display pages. Unlike ListViews and RecyclerViews, the swipe will stop on the next Page. We often see examples of FragmentViewPager but less often from his simpler View centric one.

We will learn, here, how to add a ViewPager on our Activity.

ViewPager

Data

For this example, we will create an object that will hold the data that will be displayed in one TextView.

public class PageData {  
    final String text;

    public PageData(String text) {
        this.text = text;
    }
}

Layout

Now we write the MainActivity Layout including the ViewPager. Do not forget to add support-v4 in your build.graddle.

<?xml version="1.0" encoding="utf-8"?>  
<RelativeLayout  
    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=".activity.main.MainActivity">

    <android.support.v4.view.ViewPager
        android:id="@+id/activity_main_viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v4.view.ViewPager>
</RelativeLayout>  

Adapter

A ViewPager is not a lot different from a ListView, and it needs his PagerAdapter to handle his data.

To make it work we need to implement four methods:
- instantiateItem - getCount - isViewFromObject - destroyItem

If you forget isViewFromObject, your ViewPager will not display the view.
If you forget destroyItem, you will get a crash when the ViewPager tries to remove unused Views.

Let's create the PagerAdapter.

public class MainActivityPagerAdapter extends PagerAdapter {

    private final List<PageData> itemList;

    public MainActivityPagerAdapter(List<PageData> itemList) {
        this.itemList = itemList;
    }

    @Override // here we create the view that will be displayed at <position>
    public Object instantiateItem(ViewGroup container, int position) { 
        // inflates the view but not attaching it to his parent
        View view = LayoutInflater.from(container.getContext()).inflate(R.layout.item_page_data, container, false);
        bindData(view, position);
        container.addView(view);
        return view;
    }

    private void bindData(View view, int position) {
        PageData pageData = itemList.get(position);

        TextView textView = (TextView) view.findViewById(R.id.item_page_data_text);
        textView.setText(pageData.text);
    }

    @Override // let the ViewPager know how much items we display
    public int getCount() {
        return itemList.size(); 
    }

    @Override // in the instantiateItem method we return an object. Here we compare it to the view added via the inflator
    public boolean isViewFromObject(View view, Object object) { 
        return view == object; 
    }

    @Override // when the item is not in the currently displayed page nor in the buffer ones, we remove it 
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView((View) object); 
    }
}

In the instantiateItem, I sometimes see inflating views like this: View view = container.inflate(context, R.layout.item_page_data, null);. There is two "errors":
- Do not inject the activity's context into your ViewPager if you can get it from another way (container.getContext() by example) - Do not set null as root in your inflater

As Sean Farrell explains it weell in Understanding Android's LayoutInflater.inflate()

Lint will now warn you not to pass in null for root. Your app won’t crash in this scenario, but it can misbehave. When your child View doesn’t know the correct LayoutParams for its root ViewGroup, it will try to determine them on its own using generateDefaultLayoutParams. These default LayoutParams might not be what you desired. The LayoutParams that you specified in XML will get ignored. We might have specified that our child View should match the width of our parent View, but ended up with our parent View wrapping its own content and ending up much smaller than we expected.

Activity

The final step is to tie everything together.

public class MainActivity extends AppCompatActivity {  
    @BindView(R.id.activity_main_viewpager) ViewPager activityMainViewpager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        initializeViews();
    }

    private void initializeViews() {
        List<PageData> pageDataList = getPageDataList();
        MainActivityPagerAdapter adapter = new MainActivityPagerAdapter(pageDataList);
        activityMainViewpager.setAdapter(adapter);
    }

    private List<PageData> getPageDataList() {
        List<PageData> dummyDataList = new ArrayList<>();
        dummyDataList.add(new PageData("This is going to be"));
        dummyDataList.add(new PageData("LEGEN"));
        dummyDataList.add(new PageData("... wait for it ..."));
        dummyDataList.add(new PageData("DARY"));

        return dummyDataList;
    }
}

Here we go, our ViewPager is displayed.

Simple View pager animated

Adding a Pager Indicator

A Pager Indicator is a View that shows the current page and let the user know where he is. It can be dots, tabs, titles, or other.
On the support-v4 library we can find PagerTabStrip, PagerTitleStrip that we will study. You can find a lot more of other displays with the ViewPagerIndicator, PagerSlidingTabStrip, CircleIndicator, etc...

Android Support-v4 library

To be able to display a title we need to Override the getPageTitle(int position) method by providing a title in the adapter.
You can use a simple switch case with a title, but I don't find this clean. Why?
First, you will need to inject the activity context to be able to use the resources and access the multiple languages version via the getString().
Then, If you need to add a page at one point in time, you will have to modify the switch case and the itemList accordingly. In my opinion, it's error prone.

So let's start by modifying our data holder.

public class PageData {  
    final String title;
    final String text;

    public PageData(String title, String text) {
        this.title = title;
        this.text = text;
    }
}

We need now to add the title when creating the data.

// MainActivity
private List<PageData> getPageDataList() {  
   List<PageData> dummyDataList = new ArrayList<>();
   dummyDataList.add(new PageData("Page 1", "This is going to be"));
   dummyDataList.add(new PageData("Page 2", "LEGEN"));
   dummyDataList.add(new PageData("Page 3", "... wait for it ..."));
   dummyDataList.add(new PageData("Page 4", "DARY"));

   return dummyDataList;
}

Finally, we Override the getPageTitle method and return the correct title.

// MainActivityPagerAdapter
@Override
public CharSequence getPageTitle(int position) {  
    return itemList.get(position).title;
}

v4.PagerTitleStrip

PagerTitleStrip will display the Title provided, show the previous and next page title not clickable.

PagerTitleStrip rendering

To use it you will need to add it into the ViewPager like below.

<android.support.v4.view.ViewPager  
    android:id="@+id/activity_main_viewpager"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.v4.view.PagerTitleStrip
        android:id="@+id/activity_main_pager_indicator"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />
</android.support.v4.view.ViewPager>  

v4.PagerTabStrip

PagerTabStrip is similar with slight differences. It is separated from the content by a divider, it underlines the selected page and let the user click on the next and previous page to navigate.

PagerTabStrip rendering

<android.support.v4.view.ViewPager  
    android:id="@+id/activity_main_viewpager"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.v4.view.PagerTabStrip
        android:id="@+id/activity_main_pager_indicator"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />
</android.support.v4.view.ViewPager>  

Offscreen Pages Buffer

By default, a ViewPager will load the previous and next page. If you have a predefined number of pages as a list of Local, Country and World Ranking. It can be useful to increase it to have a smooth scroll experience between them.

To do it, you will need to call public void setOffscreenPageLimit(int limit) and providing a higher limit (1 is the default setting). But always keep in mind the more you set, the more the device will have to retain in memory. On low-end devices and complex Views, it can cause an OutOfMemory crash and burn your UI to ashes...

To keep all tabs from the previous example (4 pages), we will need to set it to 3 offscreen pages or list size minus one (current page viewed).

//MainActivity
private void initializeViews() {  
    List<PageData> pageDataList = getPageDataList();
    MainActivityPagerAdapter adapter = new MainActivityPagerAdapter(pageDataList);
    activityMainViewpager.setAdapter(adapter);
    activityMainViewpager.setOffscreenPageLimit(pageDataList.size() - 1); // <-- new line
}

OnPageChangeListener

If you need to get an event each time the page is switched, use the code below. SimpleOnPageChangeListener saves some boilerplate code.

// MainActivity
activityMainViewpager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {  
    @Override
    public void onPageSelected(int position) {
        // your code 
        Toast.makeText(MainActivity.this, "New page selected: "+position, Toast.LENGTH_SHORT).show();
    }
});

Wrap Up

Here we are, we have done a complete ViewPager with some basic information. I'd like to speak about a lot of other tweaks on ViewPagers like the Auto Scroll Infinite View Pager that a future client will try to sneak in your UI, but it will be for another day :)

Oh, If you want to learn about the FragmentViewPager, take a look at the CodePath Android Guides. They have done a fantastic job compiling all this data.

You can find the code in my ViewPager-Code repo

Vincent Dubedout
Montreal
Android developer that settled down in Montreal three years ago. Passionate about Technology, Code, Architecture, Mobile Platforms. Avid cyclist and polo player, enjoying the outdoors.