Tab swipe between fragments using ActionBarSherlock

Introduction

First post in my new Android blog, so I thought I should start out with something lightweight, yet pretty cool: Swiping between action bar tabs using ActionBarSherlock.

The Android Developer tools include a wizard to make this for the native action bar, but this requires your project to break backwards compatibility for devices that aren’t updated to API 14 (Ice Cream Sandwich).

This easily extendable snippet of code allows you to implement swiping between tabs on all devices in any project that uses ActionBarSherlock.

The custom activity to inherit from

Instead of letting your activity inherit from SherlockActivity or any of the likes, now let your activity inherit from the following TabSwipeActivity:

TabSwipeActivity.java (on GitHub):

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

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;

import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.ActionBar.Tab;
import com.actionbarsherlock.app.ActionBar.TabListener;
import com.actionbarsherlock.app.SherlockFragmentActivity;

public abstract class TabSwipeActivity extends SherlockFragmentActivity {

    private ViewPager mViewPager;
    private TabsAdapter adapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
    	/*
    	 * Create the ViewPager and our custom adapter
    	 */
        mViewPager = new ViewPager(this);
        adapter = new TabsAdapter( this, mViewPager );
        mViewPager.setAdapter( adapter );
        mViewPager.setOnPageChangeListener( adapter );

        /*
         * We need to provide an ID for the ViewPager, otherwise we will get an exception like:
         *
         * java.lang.IllegalArgumentException: No view found for id 0xffffffff for fragment TestFragment{40de5b90 #0 id=0xffffffff android:switcher:-1:0}
		 * at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:864)
		 *
		 * The ID 0x7F04FFF0 is large enough to probably never be used for anything else
         */
        mViewPager.setId( 0x7F04FFF0 );

        super.onCreate(savedInstanceState);

        /*
         * Set the ViewPager as the content view
         */
        setContentView(mViewPager);
    }

    /**
     * Add a tab with a backing Fragment to the action bar
     * @param titleRes A string resource pointing to the title for the tab
     * @param fragmentClass The class of the Fragment to instantiate for this tab
     * @param args An optional Bundle to pass along to the Fragment (may be null)
     */
    protected void addTab(int titleRes, Class fragmentClass, Bundle args ) {
        adapter.addTab( getString( titleRes ), fragmentClass, args );
    }
    /**
     * Add a tab with a backing Fragment to the action bar
     * @param titleRes A string to be used as the title for the tab
     * @param fragmentClass The class of the Fragment to instantiate for this tab
     * @param args An optional Bundle to pass along to the Fragment (may be null)
     */
    protected void addTab(CharSequence title, Class fragmentClass, Bundle args ) {
        adapter.addTab( title, fragmentClass, args );
    }

    private static class TabsAdapter extends FragmentPagerAdapter implements TabListener, ViewPager.OnPageChangeListener {

        private final SherlockFragmentActivity mActivity;
        private final ActionBar mActionBar;
        private final ViewPager mPager;

        /**
         * @param fm
         * @param fragments
         */
        public TabsAdapter(SherlockFragmentActivity activity, ViewPager pager) {
            super(activity.getSupportFragmentManager());
            this.mActivity = activity;
            this.mActionBar = activity.getSupportActionBar();
            this.mPager = pager;

            mActionBar.setNavigationMode( ActionBar.NAVIGATION_MODE_TABS );
        }

        private static class TabInfo {
            public final Class fragmentClass;
            public final Bundle args;
            public TabInfo(Class fragmentClass,
                    Bundle args) {
                this.fragmentClass = fragmentClass;
                this.args = args;
            }
        }

        private List mTabs = new ArrayList();

        public void addTab( CharSequence title, Class fragmentClass, Bundle args ) {
            final TabInfo tabInfo = new TabInfo( fragmentClass, args );

            Tab tab = mActionBar.newTab();
            tab.setText( title );
            tab.setTabListener( this );
            tab.setTag( tabInfo );

            mTabs.add( tabInfo );

            mActionBar.addTab( tab );
            notifyDataSetChanged();
        }

        @Override
        public Fragment getItem(int position) {
            final TabInfo tabInfo = mTabs.get( position );
            return (Fragment) Fragment.instantiate( mActivity, tabInfo.fragmentClass.getName(), tabInfo.args );
        }

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

        public void onPageScrollStateChanged(int arg0) {
        }

        public void onPageScrolled(int arg0, float arg1, int arg2) {
        }

        public void onPageSelected(int position) {
            mActionBar.setSelectedNavigationItem( position );
        }

        public void onTabSelected(Tab tab, FragmentTransaction ft) {
            TabInfo tabInfo = (TabInfo) tab.getTag();
            for ( int i = 0; i < mTabs.size(); i++ ) {
                if ( mTabs.get( i ) == tabInfo ) {
                    mPager.setCurrentItem( i );
                }
            }
        }

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

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

Using TabSwipeActivity

The only extra method(s) that TabSwipeActivity supports are the addTab() methods:

void addTab(int titleRes, Class fragmentClass, Bundle args );
void addTab(CharSequence title, Class fragmentClass, Bundle args );

Each of these will add a tab and assign a fragment to that tab. Only difference between the two is that the first accepts a string resource ID for the tab title, and the second accepts any CharSequence.

Example usage

TabSwipeTestActivity.java:

import android.os.Bundle;

public class TabSwipeTestActivity extends TabSwipeActivity {

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

		addTab( "Tab 1", TestFragment.class, TestFragment.createBundle( "Fragment 1") );
		addTab( "Tab 2", TestFragment.class, TestFragment.createBundle( "Fragment 2") );
		addTab( "Tab 3", TestFragment.class, TestFragment.createBundle( "Fragment 3") );
	}

}

Note that your activity shouldn’t call setContentView(). TabSwipeActivity does that for you!

TestFragment.java:

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

public class TestFragment extends Fragment {

	public static final String EXTRA_TITLE = "title";

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		TextView txt = new TextView( inflater.getContext() );
		txt.setGravity( Gravity.CENTER );
		txt.setText( "Fragment" );

		if ( getArguments() != null && getArguments().containsKey( EXTRA_TITLE ) ) {
			txt.setText( getArguments().getString( EXTRA_TITLE ) );
		}
		return txt;
	}

	public static Bundle createBundle( String title ) {
		Bundle bundle = new Bundle();
		bundle.putString( EXTRA_TITLE, title );
		return bundle;
	}

}

15 thoughts on “Tab swipe between fragments using ActionBarSherlock

  1. Marko

    Thanks for this.

    Maybe the code should be improved to fix the android.support.v4.app.FragmentManagerImpl.saveFragmentBasicState that happens when starting Activites from Fragments. Replace FragmentPagerAdapter with FragmentStatePagerAdapter

    Reply
  2. Dave

    It’s good, but how to set in code which tab is the default one to show, as you could do with the old TabHost and tabHost.setCurrentTab(defaultTab);

    Reply
  3. chuck

    Thank you for this great tutorial. But i have a question, how can i replace the fragments inside a tab without affecting other tabs. For example, i have tab1, tab2, tab3 with their respective fragments then inside tab1, there is a button that when click will replace the current fragment inside the tab1 to a next fragment.
    Please reply, thanks in advance.

    Reply
    1. Jesper Borgstrup Post author

      This code doesn’t support replacing fragments at runtime. You’ll have to extend the TabsAdapter, and look into FragmentTransactions.

      Reply
  4. francis

    hy.thanks for your nice tutorial.however am stuck and thouth you can help me.am makin an app with ABS.I have a class that extends sherlockFragmenntsActivity which hosts 3tabs.I have a menu bar at the bottom which is bein rendered correctly from xml.the problem is that the app crashes whenever i touch any meny item.The same menu was workin fine with sherlockActivity class extended. Thanks.

    Reply
  5. pepepaco

    why to extend Fragment instead of sherlockFragment? i mean, you are using actionbarsherlock anyways.
    regards

    Reply
  6. Nick

    How from TestFragment.java (after for example clicking on button) add new Tab ( addTab( “Tab 1″, TestFragment.class, TestFragment.createBundle( “Fragment 1″) );) ?

    Reply

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>