Tuesday, June 16, 2009

StateListDrawable and TransitionDrawable

StateListDrawable and TransitionDrawable are useful classes when it comes to making custom buttons and controls.

Making a custom button is simple. First of all you need to create some graphics for the different states of your button (see the Android dev guide on basic 2d Graphics).

I created a couple NinePatchDrawable image files using GIMP and the draw9patch.bat tool included in the Android SDK under <sdk-dir>/tools/. Here they are:



Android already has its own default built-in buttons which are pretty similar to these. Of course, in a real app you can give the buttons different shapes and designs. Also, since these are NinePatch images, I could have made them smaller to save space since they'd stretch the same way anyway.

To use these in my project, I created a couple XML files under my project's res/drawable directory, pressbutton.xml and longpressbutton.xml.


<selector android="http://schemas.android.com/apk/res/android">
<item state_pressed="true" drawable="@drawable/redbutton">
<item drawable="@drawable/bluebutton">

With selectors (StateListDrawables), the object displays the first <item> underneath the <selector> tag whose attributes match the state of the object. Since the last <item> in my example doesn't have any attributes like state_pressed, it acts as a catch-all.


<transition android="http://schemas.android.com/apk/res/android">
<item drawable="@drawable/redbutton">
<item drawable="@drawable/bluebutton">

The <transition> (TransitionDrawable) is more of an animation definition, so you don't need attributes on the <item>s. It plays through the <item>s sequentially based on the argument you pass into the startTransition(milliseconds) method.

Now, getting TransitionDrawables to work is a bit trickier than using StateListDrawables. Well, at least it took me longer to figure out.

I made a subclass of Button called TransitionButton, like this:

public class TransitionButton extends Button {
private TransitionDrawable mTransition = null;
private Context mContext;

public TransitionButton(Context context) {
mContext = context;
public TransitionButton(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
public TransitionButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;

public void setTransition(TransitionDrawable transition) {
mTransition = transition;

public void setPressed(boolean pressed) {
if (pressed && mTransition != null) {
} else if (!pressed && mTransition != null) {

To use the Button and TransitionButton, as usual I put the necessary code into the onCreate(Bundle) method of my Activity. So I have something like this:

public class StateAndTransition extends Activity {

public void onCreate(Bundle savedInstanceState) {

final Context mContext = getApplicationContext();
Resources res = mContext.getResources();
TransitionDrawable transition = (TransitionDrawable) res.getDrawable(R.drawable.longpressbutton);

final Button stateButton = (Button) findViewById(R.id.statebutton);
stateButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
stateButton.setTextSize(10 + stateButton.getTextSize());

final TransitionButton longpressButton = (TransitionButton) findViewById(R.id.longpress);

longpressButton.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent m) {
if (m.getAction() == MotionEvent.ACTION_DOWN) {
} else {
return true; // The Listener consumes the Touch action



Finally, my res/layout/main.xml:

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

<Button android:id="@+id/statebutton"
android:text="Touch me"

android:text="Hold me"


Now, that will hopefully compile and run (with minor changes like adding the imports and package statements). If so, then you should see that the Button using a StateListDrawable works fine, while the TransitionButton is buggy. It looks like this at the end of the transition:

No, we don't want it to look like that. What's going on? The answer is that since we're using NinePatch images, the padding area of the images is not changed when we use a TransitionDrawable for the background. We can fix this by either using a regular Drawable (xxx.png instead of xxx.9.png) or by drawing padding lines that extend all the way along the right and bottom edges, making button text ugly unless you manually size the Buttons.

With custom Buttons though, I think most of the time you'd be using static sized images rather than NinePatch images, but that's just my opinion. I was just too lazy this time to make more than two images.

Anyway that's about it for this small tutorial. Hope it makes sense! I wrote it mostly for myself to document this procedure, since it took awhile to figure out the details.

No comments:

Post a Comment