Android Button background image pressed/highlighted and disabled states without using multiple images

In Android, if you provide custom background images for buttons, you will lose the pressed and disabled image effects. The common way to fix that is to provide additional images for those states. I’m lazy and I find this inconvenient especially during the prototyping phase of app development.

I’ve always liked the way iOS automatically handles pressed and disabled states for custom button backgrounds so I made a Button subclass that automatically darkens the background image when the button is pressed, and makes the background transparent when it is disabled. This is done by using a custom LayerDrawable for the button that contains the original background Drawable. The LayerDrawable has to be stateful and should change the background properties depending on the current state in onStateChange(). The full source explains it better:

package net.shikii.widgets;

import android.content.Context;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.LightingColorFilter;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.util.AttributeSet;
import android.widget.Button;

/**
 * Applies a pressed state color filter or disabled state alpha for the button's background
 * drawable.
 *
 * @author shiki
 */
public class SAutoBgButton extends Button {

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

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

  public SAutoBgButton(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
  }

  @Override
  public void setBackgroundDrawable(Drawable d) {
    // Replace the original background drawable (e.g. image) with a LayerDrawable that
    // contains the original drawable.
    SAutoBgButtonBackgroundDrawable layer = new SAutoBgButtonBackgroundDrawable(d);
    super.setBackgroundDrawable(layer);
  }

  /**
   * The stateful LayerDrawable used by this button.
   */
  protected class SAutoBgButtonBackgroundDrawable extends LayerDrawable {

    // The color filter to apply when the button is pressed
    protected ColorFilter _pressedFilter = new LightingColorFilter(Color.LTGRAY, 1);
    // Alpha value when the button is disabled
    protected int _disabledAlpha = 100;

    public SAutoBgButtonBackgroundDrawable(Drawable d) {
      super(new Drawable[] { d });
    }

    @Override
    protected boolean onStateChange(int[] states) {
      boolean enabled = false;
      boolean pressed = false;

      for (int state : states) {
        if (state == android.R.attr.state_enabled)
          enabled = true;
        else if (state == android.R.attr.state_pressed)
          pressed = true;
      }

      mutate();
      if (enabled && pressed) {
        setColorFilter(_pressedFilter);
      } else if (!enabled) {
        setColorFilter(null);
        setAlpha(_disabledAlpha);
      } else {
        setColorFilter(null);
      }

      invalidateSelf();

      return super.onStateChange(states);
    }

    @Override
    public boolean isStateful() {
      return true;
    }
  }

}

To use this, just replace your original button declarations like this:

<Button
  android:layout_width="wrap_content" android:layout_height="wrap_content"
  android:background="@drawable/button_blue_bg"
  android:text="Button with background image" />

To this:

<net.shikii.widgets.SAutoBgButton
  android:layout_width="wrap_content" android:layout_height="wrap_content"
  android:background="@drawable/button_blue_bg"
  android:text="Button with background image" />

Here’s a sample output using this custom button:

The code is also available on GitHub.

  • ejoncas

    Nice workaround! It was very useful. Thanks!

  • Dr.jacky

    hi. i need this code for listview , but i can’t change it. please help me

  • Dr.jacky

    i write it for listview and its okey without any error. but its not work on listview . no effect!

  • Victor Guedes

    Nice work!

  • kishore

    Thanks for the post, how to make the button to its initial state after clicking it. What if we stay in the same activity after the button is clicked.

  • Rajesh

    I used your SAutoBgButton it’s work nice.But when I change state from disable to enable ,Button enabled but background is not change to previous (Enabled) State.

  • Shiki

    @Rajesh, did you try the Github version? https://github.com/shiki/android-autobgbutton

    Someone just sent a patch recently to fix the enabled state. Let me know.

  • Hedgehog

    Excellent work. I had many buttons and thought I’d have to put together multiple images and xml files for all of them. This is great.

  • plancer10

    Hi, Thanks for this post. I have tried this but setBackgroundDrawable(layer) is deprecated. What should I use now?

  • Pingback: Android – Dropping shadow in button when pressed | Kalyan Vishnubhatla's Blog

  • http://inlocaltv.com/blog/view/54359/solar-driven-nikon-rechargers-an-upcoming-trend new bright 9.6v battery charger

    Hi there, You’ve done an incredible job here. I am here for the first time. I happened upon this post and I find It truly useful & it helped me out a lot. I hope to give something back and aid others like you helped me. Thank you, very much.

  • http://www.theslendermanonline.tk/Contact/ www.theslendermanonline.tk

    Wow, this paragraph is nice, my younger sister is analyzing these things, so I am going to tell her.

  • chungheePark

    Thanks for you.

  • MANAGEDATA

    Replace function “setBackgroundDrawable” with “setBackground”

    /* @Override public void setBackgroundDrawable(Drawable d) { // Replace the original background drawable (e.g. image) with a LayerDrawable that // contains the original drawable. SAutoBgButtonBackgroundDrawable layer = new SAutoBgButtonBackgroundDrawable(d); super.setBackgroundDrawable(layer); } */ @Override public void setBackground(Drawable d) { SAutoBgButtonBackgroundDrawable layer = new SAutoBgButtonBackgroundDrawable(d); super.setBackground(layer);

      }
    

  • MANAGEDATA

    Fix for enabled problem:

    protected int _disabledAlpha = 100; protected int _enabledAlpha = 255; // ADD THIS LINE

          mutate();
          if (enabled && pressed) {
    

    setColorFilter(_pressedFilter); } else if (!enabled) { setColorFilter(null); setAlpha(_disabledAlpha); } else { setColorFilter(null); setAlpha(_enabledAlpha); // ADD THIS LINE }

  • sam

    Thanks you