Creating a custom Android button with a resizable skin

Source code

If you are developing your own Android application, maybe you would like to give it a custom skin with a defined set of colors for all the interface elements. Here I’m going to show how to customize Android buttons. This is the same kind of customization I made for my own app which is called FWebLauncher and you can download for free from the Android Market. In this post I’m going to quickly describe the steps that have to be performed to obtain our result, but I recommend you to download the whole example application with all the resource images through the link on top of this post.

First of all, you need to decide how your buttons will look in different states, so you’ve got to define the corresponding skin images for the Normal, Pressed, Focused and Disabled states. Of course, if you don’t need all the states, you can just define some of them. In the following image you can see how our buttons will look like:

We want the skin to be resizable, so the button can adapt its size depending on the content (i.e. the text) without making the skin look weird. To do this, we need to define a 9-patch for each image. There’s a tool installed with the Android SDK that you can find in ${ANDROID_SDK_INSTALL_DIRECTORY}\tools\draw9patch.bat. Execute that batch file and you’ll have the following application running:

As you can see I’ve already loaded the PNG file for the Normal state of our buttons and I’ve defined the 9-patch with the black lines that you can see on every side of the image. As written in the Android developer’s documentation, the top and left lines define the stretchable area used to resize the image, while the bottom and right lines define the area where the content must be placed. With the 9-patch tool, I created a new file for each image of the button skin, so for example, if the skin image for the Normal state of the button is called button_normal.png, then the corresponding 9-patch file will be button_normal.9.png and we need only this inside the resources folders of our application.

The next thing to do is creating a selector that tells Android how it should deal with the skin images depending on the button state. The selector is just an XML file that looks like this:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
	<item android:drawable="@drawable/button_disabled" android:state_enabled="false"/>
	<item android:drawable="@drawable/button_pressed" android:state_pressed="true"/>
	<item android:drawable="@drawable/button_focused" android:state_focused="true"/>
	<item android:drawable="@drawable/button_normal"/>
</selector>

In the selector you define which skin image should be used to render each button state. So if the selector XML file is called button.xml, you can just write something like this to have your button skin working:

<Button android:id="@+id/textBtn"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:text="Text in the button"
	android:background="@drawable/button"
	android:textColor="#ffffffff"/>

This is just a simple button with some text in it. In case you need a button with an image, like an icon, and you want it to resize proportionally, then you don’t need to define a 9-patch and you need to use something different like an ImageButton:

<ImageButton android:id="@+id/iconBtn"
	android:layout_width="64dip"
	android:layout_height="64dip"
	android:src="@drawable/icon_button"
	android:scaleType="fitCenter"
	android:background="#00000000"/>

These are the possible states for iconBtn:

I set the scaleType attribute to make the image resize correctly and I set the background to #00000000 because I want to make it completely transparent (remember that the format for that attribute is ARGB where A is the alpha value). I don’t need the background because I already have an image for each state of the button with its own trasparency correctly set on the borders to have a custom shape for the button if I need it. The src attribute specifies the selector to use for this button:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
	<item android:drawable="@drawable/icon_button_disabled" android:state_enabled="false"/>
	<item android:drawable="@drawable/icon_button_pressed" android:state_pressed="true"/>
	<item android:drawable="@drawable/icon_button_focused" android:state_focused="true"/>
	<item android:drawable="@drawable/icon_button_normal"/>
</selector>

What if you want a button with both an icon and the text? Well, you can do it with something like this:

<?xml version="1.0" encoding="utf-8"?>
<Button android:id="@+id/textAndIconBtn"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:text="Text in the button"
	android:background="@drawable/button"
	android:drawableLeft="@drawable/star_icon"
	android:drawablePadding="5dip"
	android:textColor="#ffffffff"/>

The icon is specified in the drawableLeft attribute and you can set also a padding to use between the icon and the text through the drawablePadding attribute. In this case the button.xml selector makes it possible to change only the background image depending on the button state, but not the icon itself. To do that, you need to act programmatically in the source code.

To see the final result of what I explained, take a look at the example application downloadable from the top of this post:

For each kind of button, you can try to make it pressed, focused or disabled to see how it changes. The layout of the main activity is defined as follows:

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

	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="Button with text:"/>

	<LinearLayout
		android:orientation="horizontal"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_marginTop="3dip"
		android:layout_marginBottom="3dip">

		<Button android:id="@+id/textBtn"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:text="Text in the button"
			android:background="@drawable/button"
			android:textColor="#ffffffff"
			android:layout_gravity="center_vertical"/>

		<CheckBox android:id="@+id/textBtnEnabledCheck"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:layout_marginLeft="10dip"
			android:checked="true"
			android:text="Enabled"
			android:layout_gravity="center_vertical"/>

	</LinearLayout>

	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="Button with icon:"
		android:layout_marginTop="6dip"/>

	<LinearLayout
		android:orientation="horizontal"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_marginTop="3dip"
		android:layout_marginBottom="3dip">

		<ImageButton android:id="@+id/iconBtn"
			android:layout_width="64dip"
			android:layout_height="64dip"
			android:src="@drawable/icon_button"
			android:scaleType="fitCenter"
			android:background="#00000000"
			android:layout_gravity="center_vertical"/>

		<CheckBox android:id="@+id/iconBtnEnabledCheck"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:layout_marginLeft="10dip"
			android:checked="true"
			android:text="Enabled"
			android:layout_gravity="center_vertical"/>

	</LinearLayout>

	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="Button with text and icon:"
		android:layout_marginTop="6dip"/>

	<LinearLayout
		android:orientation="horizontal"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_marginTop="3dip"
		android:layout_marginBottom="3dip">

		<Button android:id="@+id/textAndIconBtn"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:text="Text in the button"
			android:background="@drawable/button"
			android:drawableLeft="@drawable/star_icon"
			android:drawablePadding="5dip"
			android:textColor="#ffffffff"
			android:layout_gravity="center_vertical"/>

		<CheckBox android:id="@+id/textAndIconBtnEnabledCheck"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:layout_marginLeft="10dip"
			android:checked="true"
			android:text="Enabled"
			android:layout_gravity="center_vertical"/>

	</LinearLayout>

</LinearLayout>

In the MainActivity class we just define the listeners to make the buttons enabled or disabled depending on the checkboxes. Note that you need to set the value also for the clickable property of the ImageButton to make it actually enabled or disabled.

package com.devahead.customandroidbuttonwithresizableskin;

import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageButton;

public class MainActivity extends Activity implements OnCheckedChangeListener
{
	// Interface elements
	protected Button textBtn;
	protected CheckBox textBtnEnabledCheck;
	protected ImageButton iconBtn;
	protected CheckBox iconBtnEnabledCheck;
	protected Button textAndIconBtn;
	protected CheckBox textAndIconBtnEnabledCheck;

	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		
		// Retrieve interface elements
		textBtn = (Button)findViewById(R.id.textBtn);
		textBtnEnabledCheck = (CheckBox)findViewById(R.id.textBtnEnabledCheck);
		iconBtn = (ImageButton)findViewById(R.id.iconBtn);
		iconBtnEnabledCheck = (CheckBox)findViewById(R.id.iconBtnEnabledCheck);
		textAndIconBtn = (Button)findViewById(R.id.textAndIconBtn);
		textAndIconBtnEnabledCheck = (CheckBox)findViewById(R.id.textAndIconBtnEnabledCheck);

		// Add listeners
		textBtnEnabledCheck.setOnCheckedChangeListener(this);
		iconBtnEnabledCheck.setOnCheckedChangeListener(this);
		textAndIconBtnEnabledCheck.setOnCheckedChangeListener(this);
	}

	@Override
	public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
	{
		if (buttonView == textBtnEnabledCheck)
		{
			textBtn.setEnabled(isChecked);
		}
		else if (buttonView == iconBtnEnabledCheck)
		{
			iconBtn.setEnabled(isChecked);
			iconBtn.setClickable(isChecked);
		}
		else if (buttonView == textAndIconBtnEnabledCheck)
		{
			textAndIconBtn.setEnabled(isChecked);
		}
	}
}

That’s it. I hope you can find something useful in this post and if you want to see some custom buttons in action in a real application, you could give a try to FWebLauncher.

Comments

Hoshang
Reply

thank you very much for the tutorial :)

HoShang

Faizan
Reply

Brother Good tutorial for newbies like me

Leave a comment

name*

email* (not published)

website