android:orientation="vertical" does not work for TabWidget

aiboman picture aiboman · Nov 11, 2010 · Viewed 14.9k times · Source

My app has a tabhost with four tabs, and now I'm trying to make nice layouts for landscape mode. In order to make use of additional horizontal space I want to put TabWidget at the right side of the screen, and of cource all tabs must be one under another (like in a column). But when switching to landscape mode all tabs get aligned in a row, it looks quite ugly. How to fix that?

Screenshot

<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >
    <LinearLayout

        android:layout_width="fill_parent"
        android:layout_height="fill_parent" 
        >

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="wrap_content"
            android:layout_height="fill_parent"
            android:layout_weight="1">

            <include layout="@layout/filter_wizard"/>
            <include layout="@layout/filter_wizard"/>
            <include layout="@layout/filter_wizard"/>
            <include layout="@layout/filter_wizard"/>

            </FrameLayout>

             <TabWidget
            android:background="#75ffffff"
            android:id="@android:id/tabs"
            android:layout_width="wrap_content" android:orientation="vertical" 
            android:layout_height="fill_parent" android:layout_weight="0" />



    </LinearLayout>
</TabHost>

Answer

Timores picture Timores · Mar 23, 2011

This is how I set up the TabHost to display the tabs on the left of the screen, with tabs vertically stacked.

One needs to set up 2 different layouts for the activity, one in portrait ("normal") mode, one in landscape mode. This implies to not use TabActivity.

I copied the layout used by TabActivity into my own project and called it main_view.xml (stored in res/layout). Here it is:

<TabHost xmlns:android="http://schemas.android.com/apk/res/android" 
         android:id="@+id/tabHost"
         android:layout_width="match_parent" 
         android:layout_height="match_parent">
    <LinearLayout android:orientation="vertical"
                  android:layout_width="match_parent" 
                  android:layout_height="match_parent">
        <TabWidget android:id="@android:id/tabs"
                   android:layout_height="wrap_content" 
                   android:layout_width="match_parent"
                   android:layout_weight="0" />
        <FrameLayout android:id="@android:id/tabcontent"
                     android:layout_width="match_parent" 
                     android:layout_height="0dip"
                     android:layout_weight="1"/>
    </LinearLayout>
</TabHost>

One must reuse the Android ids tabs and tabcontent.

In landscape, I changed this by inverting the layout height/width attributes for all controls and setting the orientation of LinearLayout to horizontal (the TabWidget and FrameLayout must be next to each other, horizontally). Here is the result, in res/layout-land, also called main_view.xml:

<TabHost   xmlns:android="http://schemas.android.com/apk/res/android" 
           android:id="@+id/tabHost"
           android:layout_width="match_parent" 
           android:layout_height="match_parent">
    <LinearLayout android:orientation="horizontal"
                  android:layout_width="match_parent" 
                  android:layout_height="match_parent">
        <TabWidget android:id="@android:id/tabs" 
                   android:layout_height="match_parent" 
                   android:layout_width="wrap_content"
                   android:layout_weight="0" />
        <FrameLayout android:id="@android:id/tabcontent"
                     android:layout_height="match_parent" 
                     android:layout_width="0dip"
                     android:layout_weight="1"/>
    </LinearLayout>
</TabHost>

Note that if you want the tabs on the right, you put the TabWidget after the FrameLayout in the XML above.

TabWidget is itself a LinearLayout. Notice that I did not set the orientation in XML. This because TabWidget does it in its own code (yes, it is hard-coded). To counter this, one has to re-set the orientation in code. Here is how I did it in my activity's oncreate

setContentView(R.layout.main_view);

final TabHost tabHost = (TabHost) findViewById(R.id.tabHost);
tabHost.setup();

Resources res = getResources();
Configuration cfg = res.getConfiguration();
boolean hor = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE;

if (hor) {
    TabWidget tw = tabHost.getTabWidget();
    tw.setOrientation(LinearLayout.VERTICAL);
}

As TabHost is created through setContentView, one must call its setup method explicitly.

The usual way to create a tab is to call:

tabHost.addTab(tabHost.newTabSpec("tab name").setIndicator("title", icon).setContent(...));

The setIndicator method, taking a title string and a drawable as parameters, creates a layout that is valid only in portrait mode. One has to create one's own view and give it to setIndicator. It is enough to copy the TabSpec.LabelAndIconIndicatorStrategy.createIndicatorView code:

   private View createIndicatorView(TabHost tabHost, CharSequence label, Drawable icon) {

       LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);

       View tabIndicator = inflater.inflate(R.layout.tab_indicator,
               tabHost.getTabWidget(), // tab widget is the parent
               false); // no inflate params

       final TextView tv = (TextView) tabIndicator.findViewById(R.id.title);
       tv.setText(label);

       final ImageView iconView = (ImageView) tabIndicator.findViewById(R.id.icon);
       iconView.setImageDrawable(icon);

       return tabIndicator;
   }

The difference with the original Google code is that the view layout itself, the TextView and ImageView ids are from our own application, not Android internal ids.

For portrait mode, we can reuse the tab_indicator.xml from Android, that we store in res/layout:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="0dip"
    android:layout_height="64dip"
    android:layout_weight="1"
    android:layout_marginLeft="-3dip"
    android:layout_marginRight="-3dip"
    android:orientation="vertical"
    android:background="@drawable/tab_indicator">

    <ImageView android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
    />

    <TextView android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        style="?android:attr/tabWidgetStyle"
    />

</RelativeLayout>

Again, this is identical to the original Android XML, except for the ids. For a landscape-friendly version, we need to invert again the layout width and height attributes. Which gives us in res/layout-land:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="64dip"
    android:layout_height="0dip"
    android:layout_weight="1"
    android:layout_marginTop="-3dip"
    android:layout_marginBottom="-3dip"
    android:orientation="vertical"
    android:background="@drawable/tab_indicator">

    <ImageView android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
    />

    <TextView android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        style="?android:attr/tabWidgetStyle"
    />
</RelativeLayout>

(I changed marginLeft and marginRight to marginTop and marginBottom but am not that sure that it is helpful)

These last XML files reference @drawable/tab_indicator, so we need to copy it from Android source code, as well as drawable/tab_selected.9.png, drawable/tab_unselected.9.png, drawable/tab_focus.9.png.

Now creating a tab becomes:

tabHost.addTab(tabHost.newTabSpec(AllTabName)
                .setIndicator(createIndicatorView(tabHost, "tab title", icon)))
                .setContent(this));

EDIT: a demo project is available at: VerticalTabHost on SkyDrive