Android Swipe (Touch Gesture) Views Using ViewPager, FragmentPagerAdapter and FragmentStatePagerAdapter With Action Bar Tabs

Swipe Views are an efficient way to allow the user to laterally navigate among related data items spread across multiple panes using a simple touch gesture (swipes/flings), making access to data faster and much more enjoyable. Think of the android home screen where you can swipe across multiple sibling screens or the facebook or twitter app with multiple screens (and their respective tabs) where you can just swipe to navigate through them. The entire experience is super interactive and fun. They’re basically equivalent to slideshows/carousels which has sections with/without tabs (or similar controls to navigate) in the web development arena.

Android provides us with the ViewPager class that allows the user to flip left and right through pages of data. To use it, you’ll have to put the <ViewPager> element (from the support library) inside your XML layout file that’ll contain multiple child views (which will be the different pages). This is how it’s done:

What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.

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

Once this code is placed in say res/layout/activity_main.xml, we’ll have to hook the layout to a PagerAdapter which will populate our ViewPager with pages. Basically a PagerAdapter is responsible for creating the content for each page. You’ll most likely want to use one of the following two implementation of PagerAdapter:

  • FragmentPagerAdapter – Each page is a fragment and all of them are kept in memory, hence best to use when there’s a fixed small set of screens to be navigated through. If loads of fragments are shoved in using this adapter then it could lead to a laggy user experience. It works even better when the content of the fragments are static than something that constantly changes or gets updated.
  • FragmentStatePagerAdapter – Each page is a fragment which gets destroyed as soon as it’s not visible to the user, i.e., the user navigates to another one. Due to this behaviour it consumes much less memory but then doesn’t perform as well as its counterpart (FragmentPagerAdapter). Hence, it’s perfect in the cases where the number of pages is high or undetermined.

FragmentPagerAdapter

Let’s see how our Activity class will look like where we’ll get the ViewPager and hook a FragmentPagerAdapter to it.

public class MainActivity extends Activity {

    CustomPagerAdapter mCustomPagerAdapter;
    ViewPager mViewPager;

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

        // == Setting up the ViewPager ==

        mCustomPagerAdapter = new CustomPagerAdapter(getFragmentManager(), this);

        mViewPager = (ViewPager) findViewById(R.id.pager);
        mViewPager.setAdapter(mCustomPagerAdapter);
    }
}

Here’s our CustomPagerAdapter which is a FragmentPagerAdapter implementation:

public class CustomPagerAdapter extends FragmentPagerAdapter {

    protected Context mContext;

    public CustomPagerAdapter(FragmentManager fm, Context context) {
        super(fm);
        mContext = context;
    }

    @Override
    // This method returns the fragment associated with
    // the specified position.
    //
    // It is called when the Adapter needs a fragment
    // and it does not exists.
    public Fragment getItem(int position) {

        // Create fragment object
        Fragment fragment = new DemoFragment();

        // Attach some data to it that we'll
        // use to populate our fragment layouts
        Bundle args = new Bundle();
        args.putInt("page_position", position + 1);

        // Set the arguments on the fragment
        // that will be fetched in [email protected]
        fragment.setArguments(args);

        return fragment;
    }

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

The getCount() method specifies how many views to show in the ViewPager and getItem() generates the views at the specified positions (passed in as an argument).

What we basically do in the getItem() method is instantiate our Fragment and pass it some arguments data that’ll get used in the fragment’s (DemoFragment) onCreateView() method to populate its XML layout. Here’s how our Fragment class will look like:

public class DemoFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout resource that'll be returned
        View rootView = inflater.inflate(R.layout.fragment_demo, container, false);

        // Get the arguments that was supplied when
        // the fragment was instantiated in the
        // CustomPagerAdapter
        Bundle args = getArguments();
        ((TextView) rootView.findViewById(R.id.text)).setText("Page " + args.getInt("page_position"));

        return rootView;
    }
}

The onCreateView() method is the one that inflates the layout resource and hence instantiates the user interface for that fragment. Here’s how our res/layout/fragment_demo.xml will look like:

<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">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Page 0"
        android:id="@+id/text" />

</FrameLayout>

Pretty simple! All you need to do now is test your app and play with the ViewPager by swiping the different screens/pages in it. They should display “Page 1”, “Page 2” and “Page 3” as texts.

Tip: At times you might have multiple fragments that you’ll want to return in the getItem() method of the FragmentPagerAdapter implementation. Then you could do something like this:

switch (position) {
    case 0:
        return new FirstFragment();
    case 1:
        return new SecondFragment();
    case 2:
        return new ThirdFragment();
}

… and all of them having different layout resources that’d display different sorts of data like photo gallery, activity feed, list of messages, etc.

FragmentStatePagerAdapter

I think I’ve already explained why this PagerAdapter is useful and when it should be used. Its main advantage is low memory consumption due to the destruction of fragments as soon as they go off the screen. So as soon as the user navigates to a particular screen the one next to it is generated/rebuilt by calling getItem() in the adapter implementation. When the number of the pages are undetermined or are too many (for example in a book reader) then this adapter makes more sense than the previous one explained.

In order to understand how to work with it, all you need to do is use the previous example and instead of making the CustomPagerAdapter extend FragmentPagerAdapter, make it subclass FragmentStatePagerAdapter. Then in getItem() you could add a Log.d() to see when its called while swiping through the views inside the view pager.

By now you’ll know the differences between FragmentPagerAdapter and FragmentStatePagerAdapter and which one to use when.

Adding Tabs to Action Bar

Along with having our swiping views there has to be something that hints the user that he can swipe through for a much better user experience. Tabs can do a great job for that purpose and in Android we can use the ActionBar to quickly create them and integrate with our ViewPager making them work in conjunction. To specify that tabs should be displayed in the action bar we’ll have to set the navigation mode on the action bar object in onCreate() method like this:

final ActionBar actionBar = getActionBar();
// specify that action bar should display tabs
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

Next, we’ll have to add the tabs, specify their text and add various listeners for them.

// == Setting up the tabs ==

String[] tabs = {
        "Tab One", "Tab Two", "Tab Three"
};
ActionBar.Tab tab;
for (int i = 0; i < tabs.length; i++) {
    tab = actionBar
            .newTab() // create new tab
            .setText(tabs[i]) // set text
            .setTabListener(tabListener); // set tab listeners
    
    // add the tab to the action bar finally
    actionBar.addTab(tab);
}

Pretty straightforward piece of code. Notice the part where we set the tab listener using setTabListener(tabListener). It’s time to define the tabListener object now, whose methods will be called whenever the user changes tabs.

ActionBar.TabListener tabListener = new ActionBar.TabListener() {
    @Override
    public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
        // show the tab

        int position = tab.getPosition();
        mViewPager.setCurrentItem(position);
    }

    @Override
    public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
        // hide the tab, we don't need this in this example
    }

    @Override
    public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
        // when tab is reselected, we don't really need this
    }
};

Super simple and easy! When a tab is selected, we show the respective screen in our ViewPager by calling its setCurrentItem() method.

Instead of creating this tabListener object inside the onCreate() method we could have also made our Activity class implement ActionBar.TabListener and then overriden the methods as our activity class’s methods. The only change then required would be to modify setTabListener(tabListener) to setTabListener(this).

Likewise, when the user changes the screen inside the ViewPager with a swipe touch gesture, we should select the corresponding tab. We can do this with the help of ViewPager.OnPageChangeListener interface or the ViewPager.SimpleOnPageChangeListener class. We’ll use the latter in this case so that we don’t have to override all the methods from the interface since when we won’t be using most of them.

// When swiping between different sections, select the corresponding tab
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
    @Override
    public void onPageSelected(int position) {
        actionBar.setSelectedNavigationItem(position);
    }
});

So the entire onCreate() method will somewhat look like this:

CustomPagerAdapter mCustomPagerAdapter;
ViewPager mViewPager;

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

    // Setup the action bar for tabs
    final ActionBar actionBar = getActionBar();
    actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    actionBar.setTitle("App Title"); // If you want to set an app title

    // == Setting up the ViewPager ==

    mCustomPagerAdapter = new CustomPagerAdapter(getFragmentManager(), this);

    mViewPager = (ViewPager) findViewById(R.id.pager);
    mViewPager.setAdapter(mCustomPagerAdapter);

    // When swiping between different sections, select the corresponding tab
    mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
        @Override
        public void onPageSelected(int position) {
            actionBar.setSelectedNavigationItem(position);
        }
    });


    // == Setting up the tabs ==

    ActionBar.TabListener tabListener = new ActionBar.TabListener() {
        @Override
        public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
            // show the tab

            int position = tab.getPosition();
            mViewPager.setCurrentItem(position);
        }

        @Override
        public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
            // hide the tab, we don't need this in this example
        }

        @Override
        public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
            // when tab is reselected, we don't really need this
        }
    };

    String[] tabs = {
            "Tab One", "Tab Two", "Tab Three"
    };
    ActionBar.Tab tab;
    for (int i = 0; i < tabs.length; i++) {
        tab = actionBar
                .newTab() // create new tab
                .setText(tabs[i]) // set text
                .setTabListener(tabListener); // set tab listener
        actionBar.addTab(tab);
    }
}

If for some reason you don’t want to include tabs in the action bar but have it in the ViewPager itself as scrollable tabs then we can make use of PagerTitleStrip or PagerTabStrip that we’ll cover in another tutorial.

Conclusion

So we covered quite a bit in this tutorial. We learnt how to create a swipeable UI element with multiple screens using ViewPager and injecting the screens/pages into it through PagerAdapter implementations like FragmentPagerAdapter and FragmentStatePagerAdapter. We also saw how we can integrate tabs to the entire Activity in the action bar that works in correspondence with the ViewPager.

It’s quite interesting to note that ViewPager is not only restricted to be used with fragments but can be used with any other View like ImageView to create an image based slideshow. In that case the custom pager adapter implementation has to be of PagerAdapter directly. We’ll see how to do this in another post.

If you have any questions or want to discuss something regarding this topic, then feel free to comment below.

Author: Rishabh

Rishabh is a full stack web and mobile developer from India. Follow me on Twitter.

11 thoughts on “Android Swipe (Touch Gesture) Views Using ViewPager, FragmentPagerAdapter and FragmentStatePagerAdapter With Action Bar Tabs”

  1. Very good tutorial!
    Here is a little mistake:
    In the CustomPagerAdapter codeblock on line 5, the “HomePagerAdapter” should be “CustomPagerAdapter”.

  2. Where to download your project? The past 2 hours I copy&pasted your code into my project but it’s not working. A sample project would be helpful.

    Thanks

    1. Hi Samuel,

      I haven’t put up the project anywhere like github. I think based on the instructions if you just copy paste the code it should definitely work. If it’s not working look at the error and tell me about it. We’ll try to fix it together! 🙂

      1. Hi,

        Very good tutorial. Could you please give me github link to this sample project. Since i am new to android development it would be really help me understand it if i actually run it and see how it works.

  3. Will it be better to use getSuportActionBar() instead of getActionBar() because sometimes action bar can be null?

    1. Depends upon the API level version you’re targeting. With API level 11 and higher you can get the ActionBar by just using getActionBar(). For lower API levels, yes you’ll need to use the support library version, i.e., getSupportActionBar() and also extend ActionBarActivity, not just Activity.

      Actually it also depends upon the theme in use. For example what I just said works perfectly with the Holo themes but if you’re using the AppCompat themes (for instance to support Material design in API level < 21) then ActionBarActivity has to be used along with getSupportActionBar() to show up an Action Bar in your UI. Although you can bypass this usage by modifying your style like this (styles.xml):

      <!-- Base application theme. -->
      <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
      <!-- Customize your theme here. -->
      <item name="android:windowNoTitle">false</item>
      <item name="android:windowActionBar">true</item>
      </style>

      But then that just seems like a hack and is more work.

  4. You can add Demo Video & Images (Snaps) for better understanding of users..

    It will help your blog to get more visitors…

  5. I followed step by step of your above tutorial but while running the app I am getting an error like “Unfortunately the app has stopped”. I am not getting were I am going wrong.

  6. Hi,

    I am facing a problem related to imports.

    In MainActivity, I have imported –

    import android.app.Activity;
    import android.support.v4.view.ViewPager;
    import android.os.Bundle;
    import android.view.Menu;
    import android.view.MenuItem;

    In CustomPagerAdapter –

    import android.os.Bundle;
    import android.content.Context;
    import android.support.v4.app.Fragment;
    import android.support.v4.app.FragmentManager;
    import android.support.v4.app.FragmentStatePagerAdapter;

    In DemoFragment –

    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.TextView;

    Now in MainActivity, I am facing an error in the following line –

    mCustomPagerAdapter = new CustomPagerAdapter(getFragmentManager(), this);

    CustomPagerAdapter expects android.support.v4.app.FragmentManager; whereas getFragmentManager() returns android.app.FragmentManager.

    How to correct this?

    Regards

    Nitin

  7. In CustomPagerAdapter, try importing these:

    import android.app.Fragment;
    import android.app.FragmentManager;
    import android.content.Context;
    import android.os.Bundle;
    import android.support.v13.app.FragmentPagerAdapter;
    import android.support.v13.app.FragmentStatePagerAdapter;

Leave a Reply

Your email address will not be published. Required fields are marked *

*