Skip to content

ActionBar Tabs with Fragments

nidhi1608 edited this page Apr 11, 2014 · 58 revisions

Fragments and Tabs

There are several ways to setup tabs with fragments. The easiest is using ActionBar tabs. Note: Standard ActionBar tabs are not supported in Gingerbread, so many people use ActionBarSherlock when Gingerbread must be supported. Google has also released a support ActionBarActivity class which can be used for compatible tabs. Thankfully, both the support approaches are more or less identical in code with a few class name tweaks.

Without Gingerbread Support

To setup tabs using ActionBar and fragments that are not gingerbread compatible, you need to add a TabListener implementation to your application which defines the behavior of a tab when activated. A good default implementation is just adding this to FragmentTabListener.java

Once you have created the FragmentTabListener from this snippet within your app, setup the ActionBar and define which tabs you would like to display and attach listeners for each tab:

public class MainActivity extends FragmentActivity {

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

	private void setupTabs() {
		ActionBar actionBar = getActionBar();
		actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
		actionBar.setDisplayShowTitleEnabled(true);

		Tab tab1 = actionBar
			.newTab()
			.setText("First")
			.setIcon(R.drawable.ic_home)
			.setTag("HomeTimelineFragment")
			.setTabListener(
				new FragmentTabListener<FirstFragment>(R.id.flContainer, this, "first",
								FirstFragment.class));

		actionBar.addTab(tab1);
		actionBar.selectTab(tab1);

		Tab tab2 = actionBar
			.newTab()
			.setText("Second")
			.setIcon(R.drawable.ic_mentions)
			.setTag("MentionsTimelineFragment")
			.setTabListener(
			    new FragmentTabListener<SecondFragment>(R.id.flContainer, this, "second",
								SecondFragment.class));

		actionBar.addTab(tab2);
	}
}

Now you have fragments that work for any Android version above 3.0. If you need gingerbread support, check out the approaches below.

With ActionBarActivity Support

Google has released an updated support library "android-support-v7-appcompat" which includes official support for the ActionBar with Gingerbread compatibility. To use this, first, we need to upgrade to latest support library by opening the Android SDK Manager and verifying we have the latest "Android Support Library".

Now, we need to import the support library as a library project following the usual steps. File => Import => Existing Android Code... and then go to your sdk folder and select sdk => extras => android => support => v7 => appcompat.

Screen 1

Now, for your app, right-click and select "Properties..." and then add "android-support-v7-appcompat" as a library.

Screen 2

Make sure to setup your app to use the correct support theme within the AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.codepath.apps.codepathbootcampapp"
    ...>
    <application
        android:theme="@style/Theme.Base.AppCompat.Light.DarkActionBar" 
        ...>
</manifest>

For these compatibility items, you also need to be careful to change the menu items to use a custom prefix in res/menu/example.xml for the showAsAction instead of android:showAsAction:

<?xml version="1.0" encoding="utf-8"?>
<menu
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:myapp="http://schemas.android.com/apk/res-auto">
    <item android:id="@+id/item_menu_ok" 
          android:icon="@drawable/ic_action_ok"
          android:title="@string/ok" 
          myapp:showAsAction="ifRoom"></item>
    <item android:id="@+id/item_menu_cancel" 
          android:icon="@drawable/ic_action_cancel"
          android:title="@string/cancel" 
          myapp:showAsAction="ifRoom"></item>
</menu>

To setup tabs using ActionBar and fragments, you need to add a TabListener implementation to your application which defines the behavior of a tab when activated. A good default implementation is just adding this to SupportFragmentTabListener.java.

Once you have created the SupportFragmentTabListener from this snippet within your app, setup the ActionBar and define which tabs you would like to display and attach listeners for each tab:

public class MainActivity extends ActionBarActivity {

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

	private void setupTabs() {
		ActionBar actionBar = getSupportActionBar();
		actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
		actionBar.setDisplayShowTitleEnabled(true);

		Tab tab1 = actionBar
		    .newTab()
		    .setText("First")
		    .setIcon(R.drawable.ic_home)
		    .setTag("HomeTimelineFragment")
		    .setTabListener(new SupportFragmentTabListener<FirstFragment>(R.id.flContainer, this,
                        "first", FirstFragment.class));

		actionBar.addTab(tab1);
		actionBar.selectTab(tab1);

		Tab tab2 = actionBar
		    .newTab()
		    .setText("Second")
		    .setIcon(R.drawable.ic_mentions)
		    .setTag("MentionsTimelineFragment")
		    .setTabListener(new SupportFragmentTabListener<SecondFragment>(R.id.flContainer, this,
                        "second", SecondFragment.class));
		actionBar.addTab(tab2);
	}

}

With ActionBarSherlock

Using ActionBarSherlock, the code looks almost exactly the same but automatically supports gingerbread and below. First you must download ActionBarSherlock.zip. Import the code into Eclipse and mark the project as a library. Next, add the library to your application. Watch the video on the FAQ page for a detailed guide.

With ActionBarSherlock installed and added to your app, add the gist code as the SherlockTabListener within your application.

Once you have created the SherlockTabListener from this snippet within your app, setup the ActionBar and define which tabs you would like to display and attaches listeners for each tab:

public class ActionBarSherlockActivity extends SherlockFragmentActivity {

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

	private void setupTabs() {
		ActionBar actionBar = getSupportActionBar();
		actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
		actionBar.setDisplayShowHomeEnabled(true);
		actionBar.setDisplayShowTitleEnabled(true);

		Tab tabFirst = actionBar
			.newTab()
			.setText("First")
			.setIcon(R.drawable.ic_launcher)
			.setTabListener(
				new SherlockTabListener<FirstFragment>(R.id.flContainer, this, "First",
								FirstFragment.class));

		actionBar.addTab(tabFirst);
		actionBar.selectTab(tabFirst);

		Tab tabSecond = actionBar
			.newTab()
			.setText("Second")
			.setIcon(R.drawable.ic_launcher)
			.setTabListener(
				new SherlockTabListener<SecondFragment>(R.id.flContainer, this, "Second",
							SecondFragment.class));

		actionBar.addTab(tabSecond);
	}
}

With this approach you can have easy tab navigation switching quickly between fragments.

Reference a Fragment in the Activity

If you need to reference a fragment instance from within the activity, you can refer to the fragment by it's "tag". For example, if you created the following tab:

private final String FIRST_TAB_TAG = "first";
Tab tab1 = actionBar
  ...
  .setTabListener(new SupportFragmentTabListener<FirstFragment>(R.id.flContainer, this,
      FIRST_TAB_TAG, FirstFragment.class));

You have assigned the tag "First" to that fragment during construction and you can access the fragment for this tab later using findFragmentByTag:

FirstFragment fragmentFirst = getSupportFragmentManager().findFragmentByTag(FIRST_TAB_TAG);

This will return the fragment instance embedded for that tab.

Tab Selection

Once the ActionBar has been set to ActionBar.NAVIGATION_MODE_TABS navigation mode, we can look at the currently selected tab index with getSelectedNavigationIndex():

// Returns tab index currently selected
getSupportActionBar().getSelectedNavigationIndex();

We can set the currently selected tab by index with setSelectedNavigationItem:

// sets current tab by index to the first tab on the bar
getSupportActionBar().setSelectedNavigationItem(0);

We can also access arbitrary tabs, check the number of tabs or remove them:

// returns the tab object at position 0
getSupportActionBar().getTabAt(0); 
// returns the total number of tabs
getSupportActionBar().getTabCount();
// Removes the first tab
getSupportActionBar().removeTabAt(0); 
// Returns the currently selected tab object
getSupportActionBar().getSelectedTab();
// Select a tab given the tab object
getSupportActionBar().selectTab(someTabObject);

Using these methods described above, we can modify the tabs and their selected states at runtime.

Note: Occasionally, you might attempt to switch the current tab and load a fragment in the wrong event (for example onActivityResult) and encounter an error such as IllegalStateException: cannot perform this action after onSaveInstanceState. This is typically tied to replacing fragments onto the content pane at a time where state loss could occur. Read this article for a detailed description of the issue. See this solution for one common approach to solving this.

Styling Tabs

The easiest way to style the ActionBar tabs is using the nifty Android ActionBar Style Generator. This utility will allow you to easily drop-in a new skin for the ActionBar.

Tab Generator

Doing a custom styling of the ActionBar requires using the Styles system for declaring the look of the ActionBar and tabs more manually. We can tweak the styles of these by building a custom theme in the res/values-v14/styles.xml:

<style name="MyTheme" parent="android:Theme.Holo.Light.DarkActionBar">
    <item name="android:actionBarTabStyle">@style/MyTheme.ActionBar.Tab</item>
</style>
 
<style name="MyTheme.ActionBar.Tab">
    <item name="android:background">@drawable/tab_bar_background</item>
</style>

There are actually a few related styles for different parts of the ActionBar:

  • actionBarTabStyle – This determines the style of the individual tabs themselves including the indicator.
  • actionBarTabBarStyle – This determines the style of the overall tab bar which contains the tabs.

Browse the Styling the ActionBar official guide for a basic overview. If you are serious about wrestling tabs styles into submission though, check out these resources as well:

  • Check out this article for a detailed look at styling tabs in the action bar
  • This second article details how to style tabs as well with several examples.
  • You can supply a custom view for each tab in truly custom cases that radically changes the look and feel of the tab.
  • Consider replacing tabs with the ViewPager and using a third-party tab indicator which more easily achieves the desired effect.

Note that fully customizing tabs requires an understanding of Drawables as well.

Styling tabs with ActionBarSherlock

If you are using ActionBarSherlock, you will need to do the following to style your tabs:

Change background color of tab container:

You will need to customize 'android:actionBarTabBarStyle' in order to achieve this. 'actionBarTabBarStyle' determines the style of the overall tab bar. It includes the whole container that includes all of the tabs.

  • Create style for "Widget.Sherlock.Light.ActionBar.TabView" in res/values-v14/styles.xml.
  • Reference this style for "actionBarTabBarStyle".
<style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
        <!-- API 14 theme customizations can go here. -->
        <item name="android:actionBarTabBarStyle">@style/ActionBarTabStyle</item>
        <item name="actionBarTabBarStyle">@style/ActionBarTabStyle</item>
</style>

<style name="ActionBarTabStyle" parent="Widget.Sherlock.Light.ActionBar.TabView">
    <item name="android:background">@color/white</item>
</style>

Format ActionBarSherlock tab text

Customize "android:actionBarTabBarStyle" in in res/values-v14/styles.xml as follows:

<style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
    <!-- API 14 theme customizations can go here. -->
    ...
    <item name="android:actionBarTabTextStyle">@style/ActionBarTabTextStyle</item>
    <item name="actionBarTabTextStyle">@style/ActionBarTabTextStyle</item>    
</style>

<style name="ActionBarTabTextStyle" parent="Widget.Sherlock.ActionBar.TabText">
    <item name="android:textColor">@color/actionbar_color_selector</item>
    <item name="android:fontFamily">sans-serif-light</item>
    <item name="android:textSize">16sp</item>
    <item name="android:textAllCaps">false</item>
</style>

To define a selector to choose different text color based on tab state, create res/color/actionbar_color_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="true" android:color="@color/red" />
    <item android:color="@color/black" />
</selector>

Sometimes its required to change the text typeface depending on the tab state. e.g. You might want to change the text typeface to bold for the selected tab. We can achieve this in the 'onTabSelected' and 'onTabUnselected' methods in 'SherlockTabListener.java' class.

public void onTabSelected(Tab tab, FragmentTransaction ft) {
    ...
	String formattedName = "<b>" + tab.getText() + "</b>";
	tab.setText(Html.fromHtml(formattedName));
}

public void onTabUnselected(Tab tab, FragmentTransaction ft) {
	if (mFragment != null) {
		String formattedName = tab.getText().toString();
		tab.setText(Html.fromHtml(formattedName));
		// Detach the fragment, because another one is being attached
		ft.detach(mFragment);
	}
}

Format ActionBarSherlock indicator

Override 'actionBarTabStyle' - it determines the style of the tabs themselves. The tab is the area that includes the text, its background, and the little indicator bar under the text. If you want to customize the indicator, you need to alter this one.

  • create the tab_bar_background drawable. This will be a state list drawable, which has different visual appearance depending on whether the tab is selected and/or pressed. In the state list, we’ll reference two other drawables for when the button is selected, and we’ll just use a plain color for the unselected states.

tab_bar_background.xml:

<?xml version="1.0" encoding="utf-8"?>
 
<selector xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:state_focused="false" android:state_selected="false" android:state_pressed="false" android:drawable="@color/transparent"/>
  <item android:state_focused="false" android:state_selected="true" android:state_pressed="false" android:drawable="@drawable/tab_bar_background_selected"/>
  <item android:state_selected="false" android:state_pressed="true" android:drawable="@color/tab_highlight"/>
  <item android:state_selected="true" android:state_pressed="true" android:drawable="@drawable/tab_bar_background_selected_pressed"/>
</selector>
  • We’ll also need to create a colors.xml file to define the two colors we used in the state list drawable:.

res\values\colors.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="transparent">#000000</color>
    <color name="tab_highlight">#65acee</color>
</resources>
  • Now, we need to create the drawables for the different backgrounds. The indicator under the active tab comes from the background drawable, so in our custom version, we’ll include an indicator in the proper color. To do this, I used a hack where I create a layer list with a rectangle shape with a 2dp stroke around the exterior, then offset the rectangle so that the top, left and right sides are outside the bounds of the view, so you only see the bottom line. In the case of the “pressed” version, the fill color is set on the rectangle to indicate that it is pressed.

tab_bar_background_selected.xml

<?xml version="1.0" encoding="utf-8"?>
 
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:top="-5dp" android:left="-5dp" android:right="-5dp">
        <shape android:shape="rectangle">
            <stroke android:color="#65acee" android:width="2dp"/>
        </shape>
    </item>
</layer-list>

tab_bar_background_selected_pressed.xml

<?xml version="1.0" encoding="utf-8"?>
 
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:top="-5dp" android:left="-5dp" android:right="-5dp">
        <shape android:shape="rectangle">
            <stroke android:color="#65acee" android:width="2dp"/>
        </shape>
    </item>
</layer-list>
  • Finally, we need to set the background for the tabs to the “tab_bar_background” drawable in res/values-v14/styles.xml.
<style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">

    <!-- API 14 theme customizations can go here. -->
    ...    
    <item name="android:actionBarTabStyle">@style/Widget.Sherlock.Light.ActionBar.Tab</item>
    <item name="actionBarTabStyle">@style/Widget.Sherlock.Light.ActionBar.Tab</item>
</style>

<style name="Widget.Sherlock.Light.ActionBar.Tab">
    <item name="android:background">@drawable/tab_bar_background</item>
</style>

Finding these guides helpful?

We need help from the broader community to improve these guides, add new topics and keep the topics up-to-date. See our contribution guidelines here and our topic issues list for great ways to help out.

Check these same guides through our standalone viewer for a better browsing experience and an improved search. Follow us on twitter @codepath for access to more useful Android development resources.

Clone this wiki locally