COLOR STATE IMAGE BUTTON

The Problem

We wanted a clear distinction in the UI when the user needed to choose something to edit.  The interface would be disabled when there was nothing, enabled when there was, and to indicate this on buttons we wanted to change their color from dull grey to something else.  Android has two ways to change the color of a button based on its state.  First, you can provide a list of drawables.  This means creating separate copies of the artwork, merely colored differently.  Second, you can change the background for the button, but not the drawable it was showing.

 

What we wanted was to generate one set of artwork in greyscale and to apply different colors to it based on the state of the button.

The Solution

Internally the ImageButton is a sub-class of ImageView.  This provides a method setColorFilter() that would change the color of the drawable.  But the value for setColorFilter() is a single color, not the list we want.

 

We solve the problem by creating a new sub-class of ImageView called ColorStateImageView.  It's a simple wrapper with a member that stores the color list.  We provide an XML attribute statefulTint to set the value, or the method setStatefulTint() to programmatically change it.  The sub-class then overrides drawableStateChanged() to extract the appropriate color from the list and apply it to the drawable.

 

ImageButton itself is a simple wrapper of ImageView that adds a method onSetAlpha() and enables focus for the widget.  ColorStateImageButton is a copy of the ImageButton file, but extending ColorStateImageView.

 

ColorStateImageButton states
 
 

Sample Code

The tarball/zip file contains three development and five test files.  The development files are all that are needed to use the widget in an application.  The button supports an extra XML attribute statefulTint which should refer to a color state list (in res/color).  For example,

 

    <com.primordand.widgets.ColorStateImageButton

      xmlns:custom="http://schemas.android.com/apk/res-auto/com.primordand.widgets"

      android:id="@+id/csib_csibbut"

      android:src="@drawable/icon_test_csib"

      custom:statefulTint="@color/colorstates_csib"

      android:layout_width="wrap_content"

      android.layout_height="wrap_content" />

 

The apk/res-auto in the path for the custom namespace is needed because we've put the widget in a library, and this is how Android now imports the reference for the attribute (SDK Yools version 20).

 

The color states are defined normally.  For example, in res/color/colorstates_csib.xml put

 

    <selector

       xmlns:android="http://schemas.android.com/apk/res/android">

       <item android:state_pressed="true" android:color="#ffff0000" />

       <item android:state_enabled="true" android:color="#ffff00ff" />

       <item android:state_enabled="false" android:color="#ff666666" />

   </selector>

 

The files needed for the button are:

 

Test Program

The test program has a colored button at top, and a button in the middle to toggle between three states: disabled, enabled, and pressed (which you can also trigger by, well, pressing the button).  At the bottom is a set of buttons showing what colors you'll see as you cycle through the list, as well as the uncolored base icon.  The test is to simply check that the color changes with the state of the button.

 

ColorStateImageButton test program
 
 

The files for the test activity are:

 

Code Notes

No known restrictions on API level.  Tested back to API 8.

 

Explicitly set the initial state of the button, as ColoredStateImageView.drawableStateChanged() doesn't seem to be called when the XML is inflated.  So, if you want to start with the button disabled, do so in Java.

 

There are the usual three variants for the ColoredStateImageView constructor, with the context, with an XML attribute set, and with a default style.  You'll see in the code for the second that you can choose the default Android button theme or set it to 0, which leaves an empty area where the window background shows through (this was done for the screen shot above).  The latter is what we ended up doing because it fit in the look of the app.  The default button style android.R.attr.buttonStyle does not agree with the holo theme used in Honeycomb, but we didn't figure out how to correct this because we weren't using it.

Source

Source as tarball / zip file.

Feedback

If you have questions or comments about the code, or improvements, please contact us.