Saturday, February 18, 2012

Tutorial: Theming an Android App

In my last blog post (Tutorial: Using Layouts in your Android App), I showed how to arrange UI widgets in an Android app using different types of layouts. In this blog post I will show how to change the look and feel of an app using custom widget designs. We will continue using the same app we used in the early tutorial - BMI Calculator.

Here is the look and feel of the app before and after applying our custom theme.

Before Applying the Theme
After Applying the Theme

You can see we have changed the background colors, added a custom title bar, and changed the theme of widgets. We have already talked about changing the background color. So here I will show you how to add a custom title bar and how to change the theme of widgets.

You can use the following links to download the eclipse project and the android app that we are creating in this tutorial.

Download the eclipse project files (zipped) com.blogger.android.meda.bmicalculator-version2.1.zip
Access the Source code from github github repo
Download the free BMI Calculator app (the latest version) from the Android Market. Available in Android Market


Adding a custom Title bar

Before adding a custom title bar, we will remove the default title bar added for our application. This can be easily done by modifying the AndroidManifest.xml in the root directory of the eclipse project. (Have a look at the underlined attribute of the activity element in the following code)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.blogger.android.meda.bmicalculator"
    android:versionCode="6"
    android:versionName="2.1" >
 
    <uses-sdk android:minSdkVersion="4" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:label="@string/app_name"
            android:name=".BMICalculatorActivity" 
            android:screenOrientation="portrait"
            android:theme="@android:style/Theme.Black.NoTitleBar">

            <intent-filter >
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>
        </activity>
    </application>
</manifest>
 
Next we add a new linear layout at the top (just after the root linearLayout element) of the res/layout/main.xml file. And inside the layout we will add the project icon and the title.

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorBG" >
 
        <ImageView
            android:id="@+id/test_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dip"
            android:src="@drawable/ic_launcher" />
 
        <TextView
            android:id="@+id/textView0"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="4dip"
            android:layout_marginLeft="15dip"
            android:layout_marginRight="5dip"
            android:layout_marginTop="9dip"
            android:text="@string/app_name"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textColor="@color/colorFonts" />

    </LinearLayout>


Here the ImageView contain the icon that is shown in the title bar. It is same as the launcher icon in this case (See how to add a launcher icon for the app by following this tutorial, Create Launcher Icon For Your Android App). In the second text view, we show the string "@string/app_name", which is defined in the "res/values/strings.xml" as "BMI Calculator". And the background of the title bar is set by adding the backgroud attribute to the "@color/colorBG" which is again defined in the strings.xml.

These steps would give you the following design to the title of the BMI Calculator App.

Custom Title bar


Theming the Widgets

To get a custom look and feeling for buttons, we can provide custom images for the different states of the button. We will prepare images for the following states of the button.
  • Normal: When the button is neither pressed nor focussed.
  • Pressed: When the button is pressed.
  • Focused: When the button is in focus. Normally this state occurs, when a user select and jump between buttons using array keys in their phone.
Since we have Button and Spinner widgets, we have to create different images for each of these widget types. Not only that, I thought it is better to have different images for two kind of Spinner widgets. Spinners that show the units (in right corner) only have one downward arrow, where as the spinners show the numbers would have two arrow signs.

To prepare the images in photoshop, I started with 50X40 size image, added a rounded rectangle shape, and styled it with bevel and emboss. Here is the set of images I could come up with. (I think they are not that good looking, but just enough for our work).
Set of Images used in theming Widgets

Drawing 9-Patch Images

Before applying these images for the widgets, we should convert them in to a format called 9-patch images. This was necessary because with the different sizes of widgets (and different screen sizes of android phones/tablets), the images may stretch giving a unhandy look.

With the 9-patch format, we can control the stretching by specifying which part should be repeated when the image is stretched. For example in the onesided_spinner_normal.png shown above, we can specify not to stretch the arrow sign part, while stretching the button.

Additionally in 9-patch format, we can specify in which section in the image the text/content should be shown. For example this is useful in images like  onesided_spinner_normal.png. When we apply it to a button, we want the text of the button to not cover its arrow sign.

To create 9-patch images, android SDK provide 9-patch image editor. You can find it in tools/draw9patch (tools/draw9patch.exe) in the android SDK directory.
Lets run the draw9patch editor and open the "onesided_spinner_normal.png" that we prepared earlier.

draw9patch editor
You can see the image is opened in the left side panel. The right hand side panel shows the different looks it will get when it is stretched vertically, horizontally, and diagonally. Here what we do is put some patches (just by clicking) in the border of the image (in the left side panel) as shown below.

Making 9-patch Image (red and blue arrows are added for clarity)
Patches shown in red arrows (top and left borders) are to say that the image should be stretched only in these sections. For example there is only one patch in the top border. That mean when the image is stretched horizontally, the line of pixel below the patch would be repeated and non of the other pixels will be repeated. And the patches in the left border, make sure the arrow sign and the circle will not be stretched. You can see the sample stretched images in the right hand side panel after our patches are applied.

The blue arrows in the above screenshot (bottom and right borders) shows the patches that indicate which part should contain the text (or any other content) of the widget. (Note the patches in the bottom border; we make sure the content will not appear near the arrow sign)

When we save the image, the editor will add 9.png (in this case onesided_spinner_normal.9.png) as the extension of the image. This extension is used by android UI to identify 9-patch images and stretched accordingly. We will convert all the prepared images to 9-patch format.

Copy all the 9-patch images to the res/drawable directory of the project.

Applying Styles to Widgets

To specify the images corresponding to different states (normal, pressed, focussed) of the button widget, we will create an xml called bmi_button.xml with the following content.

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_focused="true" 
       android:state_pressed="false" 
       android:drawable="@drawable/button_focused" />

    <item android:state_focused="true" 
       android:state_pressed="true"
       android:drawable="@drawable/button_pressed" />

    <item android:state_focused="false" 
       android:state_pressed="true"
   android:drawable="@drawable/button_pressed" />

    <item android:drawable="@drawable/button_normal" />
</selector>

Here the "@drawable/button_focused" refers to the button_focused.9.png file in the res/drawable directory.

Similar to button, we would prepare two more XML files called bmi_onesided_spinner.xml and bmi_twosided_spinner.xml for each of the spinner types. (The content would be the same as above except the drawable attribute should refer to the corresponding 9-patch images).

Next we have to set the style XMLs we have created here as the backgroud of the widgets. This is simply done by changing or adding the backgroud attribute to the widgets in main.xml (The UI XML). Here is the updated section of the main.xml. (The changes are underlined.)


    <TableLayout
        android:id="@+id/tableLayout1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

 
        <TableRow
            android:id="@+id/tableRow1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="5dip"
            android:layout_marginRight="5dip"
            android:layout_marginTop="15dip" >
 
            <TextView
                android:id="@+id/textView1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="5dip"
                android:text="@string/weightLabel"
                android:textAppearance="?android:attr/textAppearanceMedium" />
 
            <Spinner
                android:id="@+id/spinner1"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_weight="2"
                android:background="@drawable/bmi_twosided_spinner"
                android:prompt="@string/weightLabel" />
 
            <Spinner
                android:id="@+id/spinner2"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dip"
                android:layout_weight="1"
                android:background="@drawable/bmi_onesided_spinner"
                android:drawSelectorOnTop="true"
                android:entries="@array/weightUnitsArray" />
        </TableRow>
 
        <TableRow
            android:id="@+id/tableRow2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="5dip"
            android:layout_marginRight="5dip"
            android:layout_marginTop="15dip" >
 
            <TextView
                android:id="@+id/textView2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="5dip"
                android:text="@string/heightLabel"
                android:textAppearance="?android:attr/textAppearanceMedium" />
 
            <Spinner
                android:id="@+id/spinner3"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_weight="2"
                android:background="@drawable/bmi_twosided_spinner"
                android:prompt="@string/heightLabel" />
 
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dip"
                android:layout_weight="1" >
 
                <Spinner
                    android:id="@+id/spinner4"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:background="@drawable/bmi_onesided_spinner"
                    android:entries="@array/heightUnitsArray" />
 
                <Button
                    android:id="@+id/button1"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignRight="@+id/spinner4"
                    android:layout_below="@+id/spinner4"
                    android:layout_marginTop="15dip"
                    android:background="@drawable/bmi_button"
                    android:onClick="calculateClickHandler"
                    android:text="@string/calculateButton" />

            </RelativeLayout>

        </TableRow>

    </TableLayout>

Here the "@drawable/bmi_button" refers to the bmi_button.xml in the res/drawable directory that we prepared earlier.

That is all you have to do. Now run the app in your phone and check the new look and feel.

New Look and Feel of the app

8 comments:

  1. thanks for sharing! although it looks a little complicated u.u
    btw, i also have a blog and a web directory, would you like to exchange links?? anyway, just let me know

    emily.kovacs14@gmail.com

    ReplyDelete
  2. I have got lots of ideas from this blog which is very useful for me that provide the right way to customize the theme in android app so thanks for that.

    Mobile App Development

    ReplyDelete
  3. There is no doubt that your article is so amzing. Thanks for sharing.

    mobile game developers

    ReplyDelete
  4. your informative information are really very much interesting thus i like this blog very much.

    Best Android Training Institute in Chennai

    ReplyDelete
  5. Wonderful blog.. Thanks for sharing informative blog.. its very useful to me..

    Mobile App Development

    ReplyDelete