Android tutorial – Screen with tabs and swipe – part III – Styling

For the third part of the tabs and swipe tutorial we’ll make our app with fully custom topbar tabs. Make sure you have already read the first two tutorials here and here.

We’ll start from the code in the first tutorial and make the needed changes to get to something like in the following GIF:
Sample

For the easy part, the adapter and the fragments don’t change at all as we will keep the old tabs and work only on the titles.
We want custom layouts for each of the tab titles so we create them as separate xml layouts. To show the various possibilities I’ve made 4 different layouts with 4 icons and each has some text in different positions relative to the icon. The xml for the first one looks like this:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="100dp"
    android:layout_height="match_parent"
    android:gravity="center">
    <ImageView
        android:id="@+id/icon"
        android:layout_width="50dp"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:src="@drawable/icon01"
        />
    <TextView
        android:id="@+id/text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignRight="@id/icon"
        android:layout_alignBottom="@id/icon"
        android:layout_marginTop="-9dp"
        android:gravity="center"
        android:singleLine="true"
        android:textColor="#000000"
        android:text="tab1"
        />
</RelativeLayout>

For better management of the positions I use a RelativeLayout most of the times, but there are many cases when this is not needed.

To load these layouts instead of the old icons we’ve made some changes to the original MainActivity as it follows:

...
public class MainActivity extends FragmentActivity implements
		ActionBar.TabListener {

	private ViewPager viewPager;
	private TabsPagerAdapter adapter;
	private ActionBar actionBar;

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

		viewPager = (ViewPager) findViewById(R.id.pager);
		actionBar = getActionBar();
		adapter = new TabsPagerAdapter(getSupportFragmentManager());

		viewPager.setAdapter(adapter);
		actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
		actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);

		LayoutInflater inflater = LayoutInflater.from(this);
		actionBar.addTab(actionBar.newTab()
                      .setCustomView(inflater.inflate(R.layout.topbar_tab1, null))
                      .setTabListener(this));
		actionBar.addTab(actionBar.newTab()
	              .setCustomView(inflater.inflate(R.layout.topbar_tab2, null))
	              .setTabListener(this));
		actionBar.addTab(actionBar.newTab()
	              .setCustomView(inflater.inflate(R.layout.topbar_tab3, null))
	              .setTabListener(this));
		actionBar.addTab(actionBar.newTab()
	              .setCustomView(inflater.inflate(R.layout.topbar_tab4, null))
	              .setTabListener(this));
...

Note tha fact that we are now using the actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM) and also for each tab we initialize the corresponding layout:
.setCustomView(inflater.inflate(R.layout.topbar_tab2, null). Of course, if we need to dynamically change some of the contents we need to inflate them first and keep some holder with the layout. Quite simple!

Now with the more interesting part, dividers and styling. This is how our new styles.xml file looks like:

<resources>
    <style name="AppTheme" parent="android:Theme.Holo.Light">
        <item name="android:actionBarTabStyle">@style/MainActionBarTab</item>
        <item name="android:actionBarTabBarStyle">@style/MainActionBar</item>
        <item name="android:windowContentOverlay">@null</item>

    </style>

    <style name="MainActionBarTab"
           parent="@android:style/Widget.Holo.Light.ActionBar.TabView">
        <item name="android:background">@drawable/tab_indicator</item>
    </style>

    <style name="MainActionBar"
           parent="@android:Widget.Holo.Light.ActionBar.TabBar">
        <item name="android:showDividers">middle</item>
        <item name="android:divider">@drawable/topbar_divider_large</item>
        <item name="android:dividerPadding">0dp</item>
    </style>
</resources>

We have a few interesting parts:
- We are inheriting a general Light theme, we could use many others
- The android:windowContentOverlay is the topbar shadow and setting it to @null in our theme will eliminate it
- actionBarTabBarStyle set to MainActionBar determines the style of the overall tab bar. It includes the whole container that holds all of the tabs
- actionBarTabStyle set to MainActionBarTab holds 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
- this theme is used in the manifest under the application tag

For the MainActionBar we should note the following:
- it inherits some ActionBar.TabBar parent from the android classic styles (or from AppCompat packs) for basic settings
- it overrides just the features we need changed – we set the dividers to be shown in the middle (in many cases the “none” selection may be of use), we use a 9patch divider and set the desired padding (in this case we want it tight to the content of the view)

For MainActionBarTab we only needed to inherit an ActionBar.TabView and override the background to get our custom tab indicators (these are the red underlines bellow the selected tab).

The tab indicators states are declared in an xml in the drawable folder as it follows:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Non focused states -->
    <!--<item android:state_focused="false" android:state_selected="false" android:state_pressed="false" android:drawable="@android:color/white" />-->
    <item android:state_focused="false" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/tab_indicator_normal" />
    <item android:state_focused="false" android:state_selected="true"  android:state_pressed="false" android:drawable="@drawable/tab_indicator_selected" />

    <!-- Focused states -->
    <item android:state_focused="true" android:state_selected="false" android:state_pressed="false" android:drawable="@android:color/white" />
    <item android:state_focused="true" android:state_selected="true"  android:state_pressed="false" android:drawable="@drawable/tab_indicator_selected" />

    <!-- Pressed -->
    <!--    Non focused states -->
    <item android:state_focused="false" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/tab_indicator_selected" />
    <item android:state_focused="false" android:state_selected="true"  android:state_pressed="true" android:drawable="@drawable/tab_indicator_selected" />

    <!--    Focused states -->
    <item android:state_focused="true" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/tab_indicator_selected" />
    <item android:state_focused="true" android:state_selected="true"  android:state_pressed="true" android:drawable="@drawable/tab_indicator_selected" />
</selector>

And now we need the normal and selected states for the indicators and we make these as xml shapes:

<?xml version="1.0" encoding="utf-8"?>
<layer-list
    xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/bg_tab_indicator_unselected_border" />
        </shape>
    </item>
    <item android:bottom="1px">
        <shape android:shape="rectangle">
            <solid android:color="@color/bg_tab_indicator" />
        </shape>
    </item>
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<layer-list
    xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/bg_tab_indicator_selected_border" />
        </shape>
    </item>
    <item android:bottom="3dp">
        <shape android:shape="rectangle">
            <solid android:color="@color/bg_tab_indicator" />
        </shape>
    </item>
</layer-list>

Bonus links
These asa some great alternatives we used as tab indicators and they worked great:
- classic indicators – viewpagerindicator, Android-ViewPagerIndicator
- for a gesture accurate sliding tab title (only with text) PagerSlidingTabStrip
- for some great animations on slide – JazzyViewPager

You can find the source code for today’s example here. This concludes our series on swipes and tabs, we hope you enjoyed it and we’ll come back soon with fresh snippets and tutorials.

Leave a Reply

You must be logged in to post a comment.