Android ViewPager Tutorial

In Android, if you need to swipe from right to left or vice-versa in order to see different data, you can use a ViewPager. In order to use it, you have to implement a PagerAdapter which will generate the pages that the view shows.

ViewPager’s adapters

We can use one of the 3 adapters provided:

  • PagerAdapter:
    • it is the base class for a ViewPager adapter
    • used when you need to display only views
  • FragmentPagerAdapter:
    • extends PagerAdapter
    • represents each page as a Fragment
    • each page is persistently kept in the fragment manager as long as the user can return to the page, but the view hierarchy can be destroyed when not visible
    • mostly used for static pages like tabs
  • FragmentStatePagerAdapter:
    • extends PagerAdapter
    • represents each page as a Fragment
    • handles saving and restoring of a Fragments’s state
    • mostly used for a large number of pages

When an adapter loads a page, it creates the adjacent pages also. This means that for position 0, it will also load the next page from position 1. And for position 1, it will load the pages from positions 0 and 2, and so on.

Fun fact: even though you can set a click listener or a longClick listener on a viewPager, they won’t work. ViewPager does NOT consume click events. You will have to set the click event on your view in instantiateItem() method.

PagerAdapter

First, we will see an example of a ViewPager that uses a PagerAdapter, this means that we will have to display only some views (in our case, some images :P)

activity_main.xml

<?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="ro.tutorial.funcode.viewpagertutorial.MainActivity">

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

</RelativeLayout>

view_pager_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/linearLayout"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@android:color/black">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

MainActivity.java

package ro.tutorial.funcode.viewpagertutorial;

import android.content.Context;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {


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

        List<Integer> images = new ArrayList<>();
        images.add(R.drawable.android_one);
        images.add(R.drawable.android_two);
        images.add(R.drawable.android_three);

        ViewPager viewPager = findViewById(R.id.viewPager);
        viewPager.setAdapter(new MyViewPagerAdapter(MainActivity.this, images));

    }

    protected class MyViewPagerAdapter extends PagerAdapter {
        private List<Integer> imageList;
        private Context context;

        MyViewPagerAdapter(Context context, List<Integer> images) {
            this.context = context;
            imageList = images;

        }

        @Override
        public int getCount() {
            return imageList.size();
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            LayoutInflater inflater = LayoutInflater.from(context);
            ViewGroup layout = (ViewGroup) inflater.inflate(R.layout.view_pager_item, container,
                    false);
            ImageView imageView = layout.findViewById(R.id.imageView);
            imageView.setImageResource(imageList.get(position));

            container.addView(layout);
            return layout;
        }

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

As you can see, when using the base adapter, we have to implement the following methods at minimum:

  • instantiateItem(ViewGroup, int): in this method we have to inflate the layout which which will represent a page for the given position.
  • destroyItem(ViewGroup, int, Object): in this method we have to write the code that removes a page from its container for the given position. This method is called while the user navigates through pages. For example if the current position is 0 and the user navigates to the next page, which will be on position 1, at this point, the method is not called. But, when the user goes again to a next page, position 2, at this point, the page from position 0 will be removed. And from now on, whenever the user swipes to a next page the method will be called for the next positions, 1, 2 and so on.
  • getCount(): here we have to provide the number of pages that the viewPager will have to display. In our case, the size of list containing our images which is 3 :)
  • isViewFromObject(View, Object): it is required for a PagerAdapter in order for it to function properly. It checks if Object (the one returned by instantiateItem(ViewGroup, int)) is the same with View.

FragmentPagerAdapter and FragmentStatePagerAdapter

In order to implement any of these 2 adapters you will have to implement just 2 methods to have a working adapter:

  • getCount()
  • getItem(int)

Of course, you can also implement methods from PagerAdapter also if you need to.

In the following example, we will use the same example as above, but using a FragmentStatePager adapter. The example is available for a FragmentPagerAdapter too, as both adapters have to implement same methods.

view_pager_item.xml and activity_main.xml will remain the same. What changes is MainActivity.java class and we have to create a new java class (a fragment), named MyFragment.java.

MyFragment.java

package ro.tutorial.funcode.viewpagertutorial;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;


public class MyFragment extends Fragment {
    private int imageResId;
    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @return A new instance of fragment Fragment1.
     */
    public static MyFragment newInstance(int drawableResId) {
        MyFragment myFragment = new MyFragment();

        Bundle args = new Bundle();
        args.putInt("drawableResId", drawableResId);

        myFragment.setArguments(args);
        return myFragment;
    }

    public MyFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        imageResId = getArguments() != null ? getArguments().getInt("drawableResId") : 0;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.view_pager_item, container, false);
        ImageView imageView = view.findViewById(R.id.imageView);
        imageView.setImageResource(imageResId);

        return view;
    }
}

MainActivity.java

package ro.tutorial.funcode.viewpagertutorial;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {


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

        List<Integer> images = new ArrayList<>();
        images.add(R.drawable.android_one);
        images.add(R.drawable.android_two);
        images.add(R.drawable.android_three);

        ViewPager viewPager = findViewById(R.id.viewPager);
        viewPager.setAdapter(new MyViewPagerAdapter(getSupportFragmentManager(), images));

    }

    protected class MyViewPagerAdapter extends FragmentStatePagerAdapter {
        private List<Integer> imageList;

        MyViewPagerAdapter(FragmentManager fm, List<Integer> images) {
            super(fm);

            imageList = images;
        }

        @Override
        public int getCount() {
            return imageList.size();
        }

        @Override
        public Fragment getItem(int position) {
            return MyFragment.newInstance(imageList.get(position));
        }
    }
}

FragmentStatePagerAdapter vs FragmentPagerAdapter

What is the difference between these 2 adapters? Well is the way they handle the life of pages. The first will destroy and recreate the pages if we implement getItemPosition() POSITION_NONE. But the latter, will not destroy them, but it will attach and detach them which makes it perfect to use for fragments that are permanent.

Fun fact: notifyDataSetChanged() doesn’t work by itself on a viewPager. It has to be used together with

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

The above method is triggered by notifyDataSetChanged().

sponsored
Exit mobile version