Tutorial: ActionBarSherlock Tabs + ViewPager + ListFragment (with tab listeners and more…)

Hi, everybody!

Today I gonna post more complex tutorial which will cover ActionBarSherlock tabs combined with ListFragment as a tab content and a ViewPager for a better user experience (this tutorial assumes that you have a basic knowledge of these classes as well as ActionBarSherlock library).

In order to begin, we need to download ActionBarSherlock library. After the download, I suggest to create a folder for our project (you can name it TabsWithViewPager) and place the extracted ActionBarSherlock’s library content in it (without a test folder as we don’t need it). Now, create an Android project with a package name com.lomza.tabs_view_pager, minimum sdk version set to 8 and target sdk version set to 17, and name the project TabsWithViewPagerProject. Also, add a class titled MainActivity which will extend SherlockFragmentActivity and override the onCreate(Bundle savedInstanceState) method. Place the project in our TabsWithViewPager folder. To this point, you should have two folders in TabsWithViewPager folder: library and TabsWithViewPagerProject.

Now, add ActionBarSherlock as a module dependency if you program in Intellij IDEA or as a library project if you’re using Eclipse. Compile the project to see if there are any errors. If there isn’t we can finally start coding! 😉

First of all,Sherlock theme should be set if we want to use a Sherlock Action Bar and other Sherlock classes. So, please add android:theme=”@style/Sherlock.__Theme.Light attribute to the application tag. This is how your AndroidManifest.xml should look like:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.lomza.tabs_view_pager"
     android:versionCode="1"
     android:versionName="1.0">
    <uses-sdk
       android:minSdkVersion="8"
       android:targetSdkVersion="17"/>
    <application
       android:label="@string/app_name"
       android:icon="@drawable/ic_launcher"
       android:theme="@style/Sherlock.__Theme.Light">
    <activity
           android:name="MainActivity"
           android:label="@string/app_name">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>
            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
    </activity>
</application>

I have chosen the Light theme because I’m using light theme action bar items.

Next, add an interface to our project. Name it ITabChangedListener and add one method declaration: void onTabChanged(int pageIndex, ActionBar.Tab tab, View tabView);
You will understand lately why we’re doing this.


package com.lomza.tabs_view_pager;

import android.view.View;
import com.actionbarsherlock.app.ActionBar;

/**
 * Custom Action Bar Tab Changed Listener.
 */
public interface ITabChangedListener {
    /**
     * This method is called when the user has changed page of the tab view.
     * @param pageIndex Index of the current page.
     * @param tab Instance of the selected tab control.
     * @param tabView Instance of the tab view.
     */
    void onTabChanged(int pageIndex, ActionBar.Tab tab, View tabView);
}

In this tutorial I’m creating four tabs so four fragments acting as containers of these tabs will be needed. Firstly, add four .xml files and name them first_tab_layout, second_tab_layout and so on.
Each of these layouts will have the same xml except for the text in the TextView – it will be First!!!, Second!!!, Third!!! and Fourth!!! respectively.

This is the first_tab_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:orientation="vertical"
         android:layout_width="match_parent"
         android:layout_height="match_parent">
<TextView
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="First!!!"/>

<ListView
       android:id="@android:id/list"
       android:layout_width="match_parent"
       android:layout_height="match_parent"/>

The id of the ListView should be @android:id/list as we will be using SherlockListFragment and the id of the list should be like this in order to properly identify it.

In this tutorial, we will be using two action bar icons: Refresh and Settings. The first one will be always visible. This is highly possible in a real life as we have a list in every tab and user should have a possibility to refresh it somehow. The Settings icon will be showed only when the third tab is selected. This is just an example, there won’t be any settings and there is nothing special about this third tab, I just picked it up randomly so you can see how to manage Action Bar items visibility depending on tab selection. You can use any Action Bar images to represent those items. Just put them into drawables folder.

Also, add this content to strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">TabsWithViewPagerProject</string>

    <!-- Action bar items -->
    <string name="action_refresh">Refresh</string>
    <string name="action_settings">Settings</string>
</resources>

Create an ids.xml file in values directory and paste the xml below:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item type="id" name="pager"/>
</resources>

The pager id will be needed for our ViewPager.

Although, there will be only one Refresh item on the Action Bar, depending on tab selected, only this current tab’s list should be refreshed. So, the items itself will be added in the MainActivity class, but their actions should be defined in every fragment separately. With this in mind, we will write a code for the first list fragment:


package com.lomza.tabs_view_pager;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.actionbarsherlock.app.SherlockListFragment;
import com.actionbarsherlock.view.MenuItem;

/**
 * This is the First tab list fragment.
 */
public class FirstFragment extends SherlockListFragment {
    private View _fragmentView;

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

    }

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

        setHasOptionsMenu(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        _fragmentView = inflater.inflate(R.layout.first_tab_layout, container, false);
        if (_fragmentView == null)
            return null;

        return _fragmentView;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getTitle().equals(getString(R.string.action_refresh))) {
            refreshList();
            return true;
        }

        return false;
    }

    /**
     * Refreshes the list.
     */
    private void refreshList() {
        // TODO
        Toast.makeText(getActivity(), "Refreshing the first list...", Toast.LENGTH_SHORT).show();
    }
}

As you can see, there is a setHasOptionsMenu(true); call in onCreate() method. Without it, a fragment won’t receive the callbacks when Action Bar items are selected. Also, onOptionsItemSelected() method was added and there is a check if the item selected is a Refresh item. If so, a Toast message will be displayed.

Add SecondFragment and FourthFragment classes with the same content as the first one, except for the layout and a Toast message text.

Add ThirdFragment and copy the code below:


package com.lomza.tabs_view_pager;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.actionbarsherlock.app.SherlockListFragment;
import com.actionbarsherlock.view.MenuItem;

/**
 * This is the Third tab list fragment.
 */
public class ThirdFragment extends SherlockListFragment {
    private View _fragmentView;

    /**
     * Shows the Settings view.
     */
    private void goToSettings() {
        // TODO
        Toast.makeText(getActivity(), "Settings...", Toast.LENGTH_SHORT).show();
    }

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

    }

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

        setHasOptionsMenu(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        _fragmentView = inflater.inflate(R.layout.third_tab_layout, container, false);
        if (_fragmentView == null)
            return null;

        return _fragmentView;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getTitle().equals(getString(R.string.action_refresh))) {
            refreshList();
            return true;
        } else if (item.getTitle().equals(getString(R.string.action_settings))) {
            goToSettings();
            return true;
        }

        return false;
    }

    /**
     * Refreshes the list.
     */
    private void refreshList() {
        // TODO
        Toast.makeText(getActivity(), "Refreshing the third list...", Toast.LENGTH_SHORT).show();
    }
}

The only difference between this and other fragments is that it has one extra item – Settings Action Bar menu item.

Now it’s time to write a tab listener. Create a MyTabListener class which extends FragmentStatePagerAdapter and implements ActionBar.TabListener with ViewPager.OnPageChangeListener. We also need to add a TabInfo class which will be used as a tag for every new tab added. Add place this class directly in MyTabListener class.


static final class TabInfo {
        private final Class<?> _class;
        private final Bundle _args;

        TabInfo(Class<?> clss, Bundle args) {
            _class = clss;
            _args = args;
        }
    }

Add all these fields in the begging of the MyTabListener class:


    private final Context _context;
    private final ActionBar _actionBar;
    private final ViewPager _viewPager;
    private final ArrayList _tabs = new ArrayList();
    private final ArrayList _tabChangedListeners = new ArrayList();

And initialize them in a constructor:


public MyTabListener(SherlockFragmentActivity activity, ViewPager pager) {
        super(activity.getSupportFragmentManager());
        _context = activity;
        _actionBar = activity.getSupportActionBar();
        _viewPager = pager;
        _viewPager.setAdapter(this);
        _viewPager.setOnPageChangeListener(this);
    }

The view pager adapter was set along with it’s page change listener.

Among the overridden methods from interfaces, there are only two we’re interested in: onPageSelected() and onTabSelected().


    @Override
    public void onPageScrolled(int position, float positionOffset,
                               int positionOffsetPixels) {
    }

    @Override
    public void onPageSelected(int position) {
        _actionBar.setSelectedNavigationItem(position);
    }

    @Override
    public void onPageScrollStateChanged(int state) {
    }

    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        Object tag = tab.getTag();
        for (int i = 0; i < _tabs.size(); i++) {
            if (_tabs.get(i) == tag) {
                _viewPager.setCurrentItem(i);
                notifyTabChangedListeners(i, tab, tab.getCustomView());
            }
        }
    }

    @Override
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
    }

    @Override
    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }

In onPageSelected() the Action Bar should be notified about the tab position, so we’re making a setSelectedNavigationItem(position) call. In onTabSelected() we iterate though all tabs and check their tags to see which one should be selected in ViewPager. We also must notify our ITabChangedListener about this change. So add a notifyTabChangedListeners() method:


private void notifyTabChangedListeners(int tabIndex, Tab tab, View tabView) {
        for (ITabChangedListener listener : _tabChangedListeners) {
            listener.onTabChanged(tabIndex, tab, tabView);
        }
    }

We also need a method for adding tabs:


    /**
     * Adds tabs to the ActionBar.
     *
     * @param tab  Tab which will added
     * @param clss Class which is connected with the tab
     * @param args Extra tab arguments
     */
    public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) {
        TabInfo info = new TabInfo(clss, args);
        tab.setTag(info);
        tab.setTabListener(this);
        _tabs.add(info);
        _actionBar.addTab(tab);
        notifyDataSetChanged();
    }

… and for adding ITabChangedListener listeners:


public void addTabChangedListener(ITabChangedListener listener) {
        _tabChangedListeners.add(listener);
    }

getCount() method will return the size of the tabs and getItem(int position) will return a new Fragment instantiated:


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

    @Override
    public Fragment getItem(int position) {
        TabInfo info = _tabs.get(position);
        return Fragment.instantiate(_context, info._class.getName(), info._args);
    }

Because the type returned is a Fragment, there can be a simple fragment in any of Action Bar tabs not only a ListFragment.

In the end, the MyTabListener class should look like this:


package com.lomza.tabs_view_pager;

import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;
import android.view.View;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.ActionBar.Tab;
import com.actionbarsherlock.app.SherlockFragmentActivity;

import java.util.ArrayList;

/**
 * This is the custom pager tab listener. Tabs are implemented in ActionBar.
 * Every tab's content is a Sherlock fragment.
 */
public class MyTabListener extends FragmentStatePagerAdapter implements ActionBar.TabListener, ViewPager.OnPageChangeListener {
    private final Context _context;
    private final ActionBar _actionBar;
    private final ViewPager _viewPager;
    private final ArrayList _tabs = new ArrayList();
    private final ArrayList _tabChangedListeners = new ArrayList();

    static final class TabInfo {
        private final Class<?> _class;
        private final Bundle _args;

        TabInfo(Class<?> clss, Bundle args) {
            _class = clss;
            _args = args;
        }
    }

    public MyTabListener(SherlockFragmentActivity activity, ViewPager pager) {
        super(activity.getSupportFragmentManager());
        _context = activity;
        _actionBar = activity.getSupportActionBar();
        _viewPager = pager;
        _viewPager.setAdapter(this);
        _viewPager.setOnPageChangeListener(this);
    }

    /**
     * Adds tabs to the ActionBar.
     *
     * @param tab  Tab which will added
     * @param clss Class which is connected with the tab
     * @param args Extra tab arguments
     */
    public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) {
        TabInfo info = new TabInfo(clss, args);
        tab.setTag(info);
        tab.setTabListener(this);
        _tabs.add(info);
        _actionBar.addTab(tab);
        notifyDataSetChanged();
    }

    public void addTabChangedListener(ITabChangedListener listener) {
        _tabChangedListeners.add(listener);
    }

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

    @Override
    public Fragment getItem(int position) {
        TabInfo info = _tabs.get(position);
        return Fragment.instantiate(_context, info._class.getName(), info._args);
    }

    private void notifyTabChangedListeners(int tabIndex, Tab tab, View tabView) {
        for (ITabChangedListener listener : _tabChangedListeners) {
            listener.onTabChanged(tabIndex, tab, tabView);
        }
    }

    @Override
    public void onPageScrolled(int position, float positionOffset,
                               int positionOffsetPixels) {
    }

    @Override
    public void onPageSelected(int position) {
        _actionBar.setSelectedNavigationItem(position);
    }

    @Override
    public void onPageScrollStateChanged(int state) {
    }

    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        Object tag = tab.getTag();
        for (int i = 0; i < _tabs.size(); i++) {
            if (_tabs.get(i) == tag) {
                _viewPager.setCurrentItem(i);
                notifyTabChangedListeners(i, tab, tab.getCustomView());
            }
        }
    }

    @Override
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
    }

    @Override
    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }
}

Finally, it’s time for the MainActivity class! =)
I will just insert its code and try to explain it below:


package com.lomza.tabs_view_pager;

import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.view.View;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;

/**
 * This is the main fragment activity with action bar tabs and view pager.
 */
public class MainActivity extends SherlockFragmentActivity implements ITabChangedListener {
    private ActionBar _actionBar;
    private ViewPager _viewPager;

    private static boolean _firstTabShowed;
    private static boolean _secondTabShowed;
    private static boolean _thirdTabShowed;
    private static boolean _fourthTabShowed;
    private static Menu _menuInstance;

    protected static MainActivity THIS = null;

    /**
     * Adds three tabs to the Action Bar (Ongoing, Previous and Best Rated tab).
     */
    private void addTabs() {
        MyTabListener myTabListener = new MyTabListener(this, _viewPager);
        myTabListener.addTabChangedListener(this);

        ActionBar.Tab firstTab = _actionBar.newTab();
        firstTab.setText("First");
        myTabListener.addTab(firstTab, FirstFragment.class, null);
        firstTab.setTabListener(myTabListener);

        ActionBar.Tab secondTab = _actionBar.newTab();
        secondTab.setText("Second");
        myTabListener.addTab(secondTab, SecondFragment.class, null);
        secondTab.setTabListener(myTabListener);

        ActionBar.Tab thirdTab = _actionBar.newTab();
        thirdTab.setText("Third");
        myTabListener.addTab(thirdTab, ThirdFragment.class, null);
        thirdTab.setTabListener(myTabListener);

        ActionBar.Tab fourthTab = _actionBar.newTab();
        fourthTab.setText("Fourth");
        myTabListener.addTab(fourthTab, FourthFragment.class, null);
        fourthTab.setTabListener(myTabListener);
    }

    /**
     * Hides all action bar menu items.
     *
     * @param menu Action bar menu instance
     */
    private void hideAllActionItems(Menu menu) {
        if (menu != null) {
            for (int i = 0; i < menu.size(); i++)
                menu.getItem(i).setVisible(false);
        }
    }

    /**
     * Initializes the action bar and sets it's navigation mode.
     */
    private void initActionBar() {
        _actionBar = getSupportActionBar();
        _actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    }

    /**
     * Initializes the view pager and sets it as a content view.
     */
    private void initViewPager() {
        _viewPager = new ViewPager(this);
        _viewPager.setId(R.id.pager);
        setContentView(_viewPager);
    }

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

        THIS = this;
        initActionBar();
        initViewPager();
        addTabs();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        _menuInstance = menu;

        menu.add(getString(R.string.action_refresh)).setIcon(R.drawable.action_refresh).setVisible(true).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
        menu.add(getString(R.string.action_settings)).setIcon(R.drawable.action_settings).setVisible(_thirdTabShowed).setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);

        return true;
    }

    @Override
    public void onTabChanged(int pageIndex, ActionBar.Tab tab, View tabView) {
        resetVisibilityFields();
        if (_menuInstance != null) {
            hideAllActionItems(_menuInstance);

            switch (pageIndex) {
                case 0:
                    showFirstTabActionItems(_menuInstance);
                    break;

                case 1:
                    showSecondTabActionItems(_menuInstance);
                    break;

                case 2:
                    showThirdTabActionItems(_menuInstance);
                    break;

                case 3:
                    showFourthTabActionItems(_menuInstance);
                    break;
            }
        }
    }

    /**
     * Sets all tabs action item visibility fields to false.
     */
    private void resetVisibilityFields() {
        _firstTabShowed = false;
        _secondTabShowed = false;
        _thirdTabShowed = false;
        _fourthTabShowed = false;
    }

    /**
     * Shows First tab action items.
     *
     * @param menu Action bar menu instance
     */
    private void showFirstTabActionItems(Menu menu) {
        if (menu != null && menu.size() == 2) {
            menu.getItem(0).setVisible(true);

            _firstTabShowed = true;
        }
    }

    /**
     * Shows Fourth tab action items.
     *
     * @param menu Action bar menu instance
     */
    private void showFourthTabActionItems(Menu menu) {
        if (menu != null && menu.size() == 2) {
            menu.getItem(0).setVisible(true);

            _fourthTabShowed = true;
        }
    }

    /**
     * Shows Third tab action items.
     *
     * @param menu Action bar menu instance
     */
    private void showThirdTabActionItems(Menu menu) {
        if (menu != null && menu.size() == 2) {
            menu.getItem(1).setVisible(true); // show Settings action bar item

            _thirdTabShowed = true;
        }
    }

    /**
     * Shows Second tab action items.
     *
     * @param menu Action bar menu instance
     */
    private void showSecondTabActionItems(Menu menu) {
        if (menu != null && menu.size() == 2) {
            menu.getItem(0).setVisible(true);

            _secondTabShowed = true;
        }
    }
}

In addTabs() method we create a MyTabListener instance and add a tab changed listener to it. After this, four tabs are added.
hideAllActionItems(Menu menu) method is needed for resetting the visibility of our Action Bar menu items. They might differ depending on tab selected and it’s visibility should be managed somehow.
In initActionBar() we just initialize the ActionBar and set it’s navigation mode to TABS.
In initViewPager() we initialize our ViewPager, set the id to it and set the content view to ViewPager.
The onCreate() method is pretty strength-forward I think… In onCreateOptionsMenu(Menu menu) the _menuInstance is initialized and two action items are added. The Refresh item visibility is always true but the Settings item should be visible only if _thirdTabShowed is true. By default, it’s not.

In onTabChanged(int pageIndex, ActionBar.Tab tab, View tabView) method the visibility of ActionBar items is reset along with their boolean indicators. Depending on page index we’re currently on, this page’s menu items are showed.

And that’s all! Build & run this project and you should see this:

Try to click on Action Bar items and see what Toast messages you get, scroll fragments from one side to another, select different tabs – everything should work!

Hope this will help somebody and thanks for reading!

Like and share:

Published by

Tonia Tkachuk

I'm an Android Developer, writing code for living and for fun. Love beautiful apps with a clean code inside. Enjoy travelling and reading.