torsdag 26 april 2012

Android : Custom Tab layouts just using XML

Switched to using a Tabhost layout in my application, the stock one felt a big large height-wise as an image is supposed to be embedded, and i just wanted to use a simple text phrase per tab. Felt a bit off a hassle to create 9-patch images for everything, found way to many in the SDK, so this is the way i did it!

So, first we need to create a really simple layout for each Tab.
layout/custom_tab.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@+id/tabTitleText"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:gravity="center_horizontal"
          android:clickable="true"
          android:padding="5dp"
          android:textSize="15sp"
          android:textStyle="bold"
          android:ellipsize="marquee"
          android:singleLine="true"
          android:textColor="@color/tab_textcolor"
          android:background="@drawable/tab_selector"/>

That's it for my Tab view! Clickable set to true because i want to color to change to white when pressing the tab. Padding of course, otherwise it'll be to small and singleline so it won't wrap around. Next our text color selector.

color/tab_textcolor.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="#FFFFFF" />
    <item android:state_pressed="true" android:color="#FFFFFF" />
    <item android:color="@android:color/darker_gray" />
</selector>

Nothing special here, text color will be white when tab is selected or pressed, otherwise gray.

drawable/tab_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:state_pressed="false" 
          android:drawable="@drawable/tab_bg_selected" />
    <item android:state_selected="false" android:state_pressed="false" 
          android:drawable="@drawable/tab_bg_unselected" />
    <item android:state_pressed="true" 
          android:drawable="@drawable/tab_bg_pressed" />
</selector>

Three different backgrounds for our TextView, selected, unselected and pressed.

drawable/tab_bg_selected.xml
<?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="#FFFFFF" />
            <corners android:topLeftRadius="5dp" android:topRightRadius="5dp"/>
        </shape>
    </item>

    <item  android:top="1dp" android:bottom="2dp" android:left="1dp" android:right="1dp">
    <shape android:shape="rectangle">
    <gradient
            android:startColor="#ff1673"
            android:endColor="#e6acc3"
            android:angle="270"
            android:type="linear"
            />
        <corners android:topLeftRadius="5dp" android:topRightRadius="5dp"/>
    </shape>
</item>
</layer-list>

Link to <layer-list> at Google. First we create a white colored rectangle (just gonna use it as a border), then we create our gradient on top of it but set the offset so we'll get white borders. Can use <stroke> but then we cant control the border, i wanted different widths and sometimes no width. Like when it's unselected.

drawable/tab_bg_unselected.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:top="10dp">
        <shape android:shape="rectangle">
            <solid android:color="#FFFFFF" />
        </shape>
    </item>
    <item android:bottom="2dp">
    <shape  android:shape="rectangle">
    <gradient android:centerColor="#655e5e" android:startColor="#3e3e3e"
              android:endColor="#807c7c"
              android:angle="-90" />
        <corners android:topLeftRadius="4dp" android:topRightRadius="4dp"/>
    </shape>
    </item>
</layer-list>


Our unselected tab background. First a white rectangle, i want a border at the bottom. Notice i placed android:top="10dp" , need to offset it so it wont be white at the top corners in the gradient.

drawable/tab_bg_pressed.xml
<?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="#FFFFFF" />
            <corners android:topLeftRadius="5dp" android:topRightRadius="5dp"/>
        </shape>
    </item>
    <item  android:top="1dp" android:bottom="2dp" android:left="1dp" android:right="1dp">
        <shape android:shape="rectangle">
            <solid android:color="#ff1673" />
            <corners android:topLeftRadius="5dp" android:topRightRadius="5dp"/>
        </shape>
    </item>
</layer-list>

Looks like the first one except i just use a solid color when it's pressed. Sneak peek of the layout :

Lets move along to the startup screen layout.
layout/main.xml
<?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"
         android:layout_marginTop="2dp">
    <LinearLayout
            android:paddingTop="2dp"
            android:orientation="vertical"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            >
        <TabWidget
                android:id="@android:id/tabs"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                />
        <FrameLayout
                android:id="@android:id/tabcontent"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                />
    </LinearLayout>
</TabHost>

Standard TabHost, taken from the Android Developers homepage. Tab Layout Example.

layout/view_testlayout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:layout_marginTop="5dp">
    <Button android:id="@+id/button_Test"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            />
</LinearLayout>


The layout for the activites we create in the tabs.


src/MyActivity.java

import android.app.TabActivity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TabHost;
import android.widget.TextView;

public class MyActivity extends TabActivity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        TabHost tabHost = getTabHost();  // The activity TabHost
        TabHost.TabSpec spec;  // Resusable TabSpec for each tab
        Intent intent;  // Reusable Intent for each tab
        // Create an Intent to launch an Activity for the tab (to be reused)
        intent = new Intent().setClass(this, TestActivity.class);
        // Create our custom view. 
        View tabView = createTabView(this, "Tab 1");
        // Initialize a TabSpec for each tab and add it to the TabHost
        spec = tabHost.newTabSpec("tab1").setIndicator(tabView)
                .setContent(intent);
        tabHost.addTab(spec);
        // Do the same for the other tabs
        tabView = createTabView(this, "Tab 2");
        intent = new Intent().setClass(this, TestActivity.class);
        spec = tabHost.newTabSpec("tab2").setIndicator(tabView)
                .setContent(intent);
        tabHost.addTab(spec);
    }

    private static View createTabView(Context context, String tabText) {
        View view = LayoutInflater.from(context).inflate(R.layout.tab_custom, null, false);
        TextView tv = (TextView) view.findViewById(R.id.tabTitleText);
        tv.setText(tabText);
        return view;
    }
}




src/TestActivity.java

import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;

public class TestActivity extends Activity {

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

        MyActivity myActivity = (MyActivity) this.getParent();
        String currentTab = myActivity.getTabHost().getCurrentTabTag();
        ((Button)findViewById(R.id.button_Test)).setText(currentTab);

    }
}


Nothing strange here, i really like the drawables through XML, works fine for basic layout! So many different .PNG's to keep track of when customizing a Tab layout. Final screenshot :


I have a small problem though, did you notice ? I did smaller corners in the unselected tab. The tab_bg_selected drawable still seems to be there even though it's not selected. If i do a larger corner radius, like 10dp it will look like this.

I'll update if i'll found out what's wrong, feel free to leave a comment if you have any idea!

Update!
Didnt really found a solution why the selected drawable still is drawn when unselected so i just updated the tab_bg_unselected.xml layer-list with a rectangle that's the same color as the background.

<?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/background_color" />
        </shape>
    </item>
    <item android:top="20dp">
        <shape android:shape="rectangle">
            <solid android:color="#FFFFFF" />
        </shape>
    </item>
    <item android:bottom="2dp">
    <shape  android:shape="rectangle">
    <gradient android:centerColor="#655e5e" android:startColor="#3e3e3e"
              android:endColor="#807c7c"
              android:angle="-90" />
        <corners android:topLeftRadius="10dp" android:topRightRadius="10dp"/>
    </shape>
    </item>
</layer-list>

No more visible corners ! : )



13 kommentarer:

  1. Thanks, I searched for this solution many days

    SvaraRadera
  2. Hi!
    I'm using Android 4.0 and i can't show icons on tabs, can you help me?
    Thanks =)

    SvaraRadera
    Svar
    1. Hi, what have you tried ? I think you should go the ActionBar route though, they've released support for API7 and higher now (if you're targeting older versions).

      Just a suggestion and good luck!

      Radera
    2. Hi, im working with a TabHost and im doing this:

      Resources res = getResources();
      TabHost tabHost = getTabHost();
      TabHost.TabSpec spec;
      Intent intent;

      intent = new Intent().setClass(this, SelectCalibracion.class);

      spec = tabHost.newTabSpec("SelectCalibracion").setIndicator("Calibracion",res.getDrawable(R.drawable.calibracion)).setContent(intent);
      tabHost.addTab(spec);

      intent = new Intent().setClass(this, MuestraDistanciasCalibracion.class);
      spec = tabHost.newTabSpec("DistanciasaBalizas").setIndicator("Balizas", res.getDrawable(R.drawable.distancias)).setContent(intent);
      tabHost.addTab(spec);

      intent = new Intent().setClass(this, Multilateracion.class);
      spec = tabHost.newTabSpec("Multilateracion").setIndicator("Ubicacion", res.getDrawable(R.drawable.multilateracion)).setContent(intent);
      tabHost.addTab(spec);

      Radera
    3. Hi again, yes. Looks like it behaves differently depending on which Android version you run. On Android 4+ you'll only see the text, set the string to "" and you'll see the icon only.

      I found this link to help you, http://stackoverflow.com/a/11379708/414581

      You need to inflate a custom view which holds both a TextView and ImageView to show them both on newer Android versions, good luck!

      Radera
  3. Hi... I follow the instructions and do not run on it. You can send me the source code?
    My email: zaithua9x@gmail.com.
    tks u! ^^

    SvaraRadera
  4. Hi
    This is good tutorial , Thanks for this Tutorial .
    my question is can we change the text color of tab text in unselected state , if yes how

    SvaraRadera
    Svar
    1. Hi! Been a while since i typed this, in the current era of Android i'd implement a ViewPager instead of using TabHost. Check out http://developer.android.com/training/implementing-navigation/lateral.html Hope it helps!

      Radera
  5. hi,
    This is good tutorial and i tanks for this
    please insert this tutorial source code

    SvaraRadera