Custom Button States


I wanted to make a ImageButton to appear differently depending on a variable. The ImageButton would behave more like a checkbox. I know that there are other options such as subclassing the CheckBox or ToggleButton. This was more of a curiosity.

My use case was I had a loop button that I wanted to display a different image when it was active.
Image of Inactive Button StateIImage of Active Button State


1)First I created a attrs.xml in res/values/
I added the following:
<resources>
<declare-styleable
name="LoopButtonState">
<attr
name="looping"
format="boolean" />
</declare-styleable>
</resources>

2)Then I subclassed the ImageButton class.
public class LoopButton extends ImageButton{

Add a int array for each for each state. In my case it is one state that is either enabled or disabled.
private static final int[] LOOPING_STATE_SET = {
R.attr.looping
};

I have a variable for tracking if looping is enabled
private boolean mLooping = false;

The standard constructors
public LoopButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

public LoopButton(Context context, AttributeSet attrs) {
super(context, attrs);
}

public LoopButton(Context context) {
super(context);
}

Then we override the onCreateDrawableState. Near as I can tell this function is what is responsible for adding in the additional state for drawing consideration. This is where your logic comes into play for determining if that state should be available for drawing. Here I am checking the isLooping variable to determine if the additional looping state should be included.
@Override
public int[] onCreateDrawableState(int extraSpace) {
// we need to pass in the number of states
// in our case we only want one extra state
final int states[] = super.onCreateDrawableState(extraSpace + 1);

if(isLooping()){
mergeDrawableStates(states, LOOPING_STATE_SET);
}
return states;
}

Simple accessor methods
public boolean isLooping(){
return mLooping;
}

public void setLooping(boolean aLoop){
mLooping = aLoop;
}

I didn’t know for the longest time that this method is what is called when an image button is clicked. I thought this was used to force a click as opposed to also being used to intercept a click. I have seen code about using the onClick method but I think that is wrong to have the class register a call back within itself. It seem sloppy. Fortunately as previously stated you can override the performClick to handle switching of states.
Here I am simply using an xor to go from one state to the other.
@Override
public boolean performClick() {

// we are going to flip the state
setLooping(mLooping ^ true);

return super.performClick();
}

3)Create an XML drawable to dictate what image to display for the two states:
Note the xmlns:app specifies the namespace and that app:looping corresponds to step ones attr name=”looping”
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/project.namespace">
<item app:looping="true" android:drawable="@drawable/loop_active" />
<item android:drawable="@drawable/loop" />
</selector>

That is it. You know have an additional state for your ImageButton.

Leave a Reply

Your email address will not be published. Required fields are marked *