Monday, April 6, 2009

Android's StateListDrawable and RadioButton Example

A radio button on the Android platform is drawn based on a label and a set of images contained in a StateListDrawable. You can create an image radio button by setting the text label to empty and setting your own images. There is no need to override the class or implement your own.

You can create your own radio buttons either via xml declaration or via code. You can see this in the Android source code but I will describe both methods in the rest of this post.

In the Android source code, you want to have a look at the following files:
StateListDrawable.java
DrawableContainer.java

RadioButton.java
CompoundButton.java

btn_radio.xml


XML DECLARATION:
When you declare your radio button, add the tag android:button="@drawable/resize_button". In your res/drawable/ directory, you must now have a resize_button.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:state_checked="true"
android:state_window_focused="false"
android:drawable="@drawable/resize_button_on" />
<item
android:state_checked="false"
android:state_window_focused="false"
android:drawable="@drawable/resize_button_off" />
<item
android:state_checked="true"
android:state_pressed="true"
android:drawable="@drawable/resize_button_on_pressed" />
<item
android:state_checked="false"
android:state_pressed="true"
android:drawable="@drawable/resize_button_off_pressed" />
<item
android:state_checked="true"
android:state_focused="true"
android:drawable="@drawable/resize_button_on_selected" />
<item
android:state_checked="false"
android:state_focused="true"
android:drawable="@drawable/resize_button_off_selected" />
<item
android:state_checked="true"
android:drawable="@drawable/resize_button_on" />
<item
android:state_checked="false"
android:drawable="@drawable/resize_button_off" />
</selector>


The order of these items seems to matter, but I haven't had the time to delve into the details. It looks like it's caused by the matching algorithm that picks a drawable from the list based on the current state. You'll notice that each image that represents a radio button state has one or more state values associated with it. The radio button class sets the state flags and the Drawable picks the best matching image to display.

By declaring the xml code just shown, I now have a graphical resize radio button that can be checked off. It won't have a label and it will have it's own icon with the usual radio button states.

VIA CODE:

 StateListDrawable drawables = new StateListDrawable();
int stateChecked = android.R.attr.state_checked;
int stateFocused = android.R.attr.state_focused;
int statePressed = android.R.attr.state_pressed;
int stateWindowFocused = android.R.attr.state_window_focused;
drawables.addState(new int[]{ stateChecked, -stateWindowFocused}, getButtonWithThumbnail(R.drawable.button_on , thumbnailBitmap));
drawables.addState(new int[]{-stateChecked, -stateWindowFocused}, getButtonWithThumbnail(R.drawable.button_off , thumbnailBitmap));
drawables.addState(new int[]{ stateChecked, statePressed }, getButtonWithThumbnail(R.drawable.button_on_pressed , thumbnailBitmap));
drawables.addState(new int[]{-stateChecked, statePressed }, getButtonWithThumbnail(R.drawable.button_off_pressed , thumbnailBitmap));
drawables.addState(new int[]{ stateChecked, stateFocused }, getButtonWithThumbnail(R.drawable.button_on_selected , thumbnailBitmap));
drawables.addState(new int[]{-stateChecked, stateFocused }, getButtonWithThumbnail(R.drawable.button_off_selected, thumbnailBitmap));
drawables.addState(new int[]{ stateChecked }, getButtonWithThumbnail(R.drawable.button_on , thumbnailBitmap));
drawables.addState(new int[]{-stateChecked }, getButtonWithThumbnail(R.drawable.button_off , thumbnailBitmap));
thumbnailBitmap.recycle();
thumbnailBitmap = null;
thumbnailButton.setButtonDrawable(drawables);
thumbnailButton.setId(viewId);
layerThumbnails.addView(thumbnailButton);
layerThumbnails.check(thumbnailButton.getId());


The selector tag in the xml file corresponds to the StateListDrawable class. Items are added with addState(). Here, I've copied the basic button drawables from android's jar file, such as R.drawable.button_off and R.drawable.button_off_pressed. I created my own function, getButtonWithThumbnail(), that takes a bitmap and combines it with the basic button image to create a custom radio button on the fly.

Notice that the true and false values of the states are specified in code using positive and negative values of the android.R.attr.state_<*> integer variables and the order in which the images are added to the drawable is the same as in the xml declaration earlier. Finally, I add the StateListDrawable to my radio button, give it an id, add it to a radio group, and simulate a click on the button by calling the check() function on the button's radio group.

9 comments:

TalkLittle said...

Helpful post. Thanks!

timotion said...

saved my day! ;)
thanks for sharing!

Hwa Rang said...

Thnx a lot mate. Pulled my hair for the proper xml attr to set the button's image instead of just the background.

lyc said...

Thanks for the info. How can i change to use truetype font? i know textview can use setTypeface method, but how about radio button?

servingHim said...

what is the size of the Android default radio button icon?

were your replacement icons the same size?

thanks!

Anonymous said...

Thanks a lot. Very helpful and precise.

Pavan Deshpande said...
This comment has been removed by the author.
Maicol Andres Reyes Ortiz said...

Hey, I'm trying to do this VIA CODE, the selector found correctly, the when i have more than one radioButton it dosen't found, when i choose one redioButton also always the last is choosen.

Maicol Andres Reyes Ortiz said...
This comment has been removed by the author.