66

I want to update the selector for a button programmatically.

I can do this with the xml file which is given below

<?xml version="1.0" encoding="UTF-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_enabled="false"
         android:drawable="@drawable/btn_off" />
   <item android:state_pressed="true"
         android:state_enabled="true" 
         android:drawable="@drawable/btn_off" />
   <item android:state_focused="true"
         android:state_enabled="true" 
         android:drawable="@drawable/btn_on" />
   <item android:state_enabled="true" 
         android:drawable="@drawable/btn_on" />
</selector>

I want to do the same thing programmatically. I have tried something like given below

private StateListDrawable setImageButtonState(int index)
{
    StateListDrawable states = new StateListDrawable();

    states.addState(new int[] {android.R.attr.stateNotNeeded},R.drawable.btn_off); 
    states.addState(new int[] {android.R.attr.state_pressed, android.R.attr.state_enabled},R.drawable.btn_off);
    states.addState(new int[] {android.R.attr.state_focused, android.R.attr.state_enabled},R.drawable.btn_on);
    states.addState(new int[] {android.R.attr.state_enabled},R.drawable.btn_on);

    return states;
}

but it didnt work.

And how to set android:state_enabled="false" or android:state_enabled="true" programatically.

Micer
  • 8,731
  • 3
  • 79
  • 73
Mani
  • 2,599
  • 4
  • 30
  • 49

5 Answers5

124

You need to use the negative value of the needed state. E.g.:

states.addState(new int[] {-android.R.attr.state_enabled},R.drawable.btn_disabled);

Notice the "-" sign before android.R.attr.state_enabled.

Zsolt
  • 2,321
  • 1
  • 20
  • 15
  • 1
    Not working in my case, it is just setting the drawable for enabled state.. what am I doing wrong here, code is just the same – Yogesh Maheshwari Aug 28 '12 at 16:23
  • @androiddeveloper I'm assuming the "-" is to define the negative of enabled as you can't define it any other way. (There is no android.R.attr.state_disabled). – ingh.am Sep 13 '13 at 11:34
  • 2
    @ing0 so it's for the "false" value of the state? i think it's a bad choice of API since it's unintuitive, plus what if some day it will be more than 2 values (true and false) ... anyway, thanks. – android developer Sep 13 '13 at 11:44
  • @androiddeveloper I don't know as I haven't tested it yet but I think that's the case. And yes, I agree it's not intuitive. – ingh.am Sep 13 '13 at 12:38
  • the order you add the states is important, if the condition to that state is met it returns that state – Alan Sep 13 '13 at 19:34
  • If you need the source for confirmation see http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.0.2_r1/android/util/StateSet.java#StateSet.stateSetMatches%28int%5B%5D%2Cint%29 – JRomero Apr 02 '15 at 23:51
  • 1
    @Osi, Do not work on Lollipop for me, any suggestions? – Defuera Mar 15 '16 at 20:17
  • you'd think this would be in the docs for addState() but it isn't! – RobP Jan 18 '19 at 23:33
  • didn't work for me . – Oussaki Nov 23 '21 at 11:26
13

Very late response here but in case anyone else is having problems setting a StateListDrawable programmatically. Then as with the XML files the order in which you set the states into the StateListDrawable is important.

For example this will work as expected:

StateListDrawable sld = new StateListDrawable();
sld.addState(new int[] { android.R.attr.state_pressed }, new ColorDrawable(Color.GRAY));
sld.addState(new int[] {}, new ColorDrawable(Color.GREEN));

This won't:

StateListDrawable sld = new StateListDrawable();
sld.addState(new int[] {}, new ColorDrawable(Color.GREEN));
sld.addState(new int[] { android.R.attr.state_pressed }, new ColorDrawable(Color.GRAY));
  • 2
    It seems the default state needs to be set LAST. This makes no sense what so ever, but doing this did get it working...*smh*... – Aggressor Jul 25 '16 at 16:20
  • Added bug report: https://code.google.com/p/android/issues/detail?id=217939&can=4&colspec=ID%20Status%20Priority%20Owner%20Summary%20Stars%20Reporter%20Opened – Aggressor Jul 25 '16 at 16:25
  • Isn't it also the same for xml statelist drawables. Default has to be always the last. I guess this order will be used to detect which pattern matches first and then will be used as source.. – Robin Vinzenz Nov 23 '17 at 12:19
7

Not enough rep to comment but the accepted answer did not work for me.

My goal was for designers to set enabled, disabled, and pressed background colors on some button. I intended for them to use it to test colors on different displays.

I noticed some other people mentioned it did not work for them either.

The states that need to be defined are

  • not pressed and enabled, this is what you see when the button is enabled and not pressed.
  • pressed and enabled, this is what you see when the button is pressed and enabled
  • not enabled. This is what you see when the button is disabled

Here is some code that will give you an idea of the states. I am using Color.Parse() to generate ints for the colors then I pass them to this method to get the StateListDrawable.

private StateListDrawable createDrawable(int enabled, int pressed, int disabled) {
  StateListDrawable stateListDrawable = new StateListDrawable();

  stateListDrawable.addState(new int[] { -android.R.attr.state_pressed, android.R.attr.state_enabled }, new ColorDrawable(enabled));
  stateListDrawable.addState(new int[] { android.R.attr.state_pressed, android.R.attr.state_enabled }, new ColorDrawable(pressed));
  stateListDrawable.addState(new int[] { -android.R.attr.state_enabled }, new ColorDrawable(disabled));

  return stateListDrawable;
}
Voski
  • 139
  • 2
  • 4
  • @androidAhmed when I built this I was using `View#setBackground(Drawable background)` https://developer.android.com/reference/android/view/View#setBackground(android.graphics.drawable.Drawable) – Voski Nov 21 '19 at 23:29
7

I am going to answer your question "How to update the selector for a BUTTON programmatically?" by proposing to switch a button for a LinearLayout with embedded ImageView and TextView. There are a number of benefits to doing this, especially if you will later decide to customize your views. There is no loss of functionality resulting from this switch. You will still be able to attach same event listeners you can attach to a button, but will be able to avoid the buttons/tabs styling nightmares. Here is a relevant code from the layout.xml

    <LinearLayout 
        android:id="@+id/button"
        style="@style/ButtonStyle">
        <ImageView 
            android:id="@+id/background"
            android:src="@drawable/custom_image"/>
        <TextView 
            style="@style/TextStyle"
            android:text="Custom Button"
            android:id="@+id/text"/>
    </LinearLayout> 

Next, I have a selector file called custom_image.xml located in the drawable folder. Here is the content of the selector file

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/disabled_img"     android:state_enabled="false" />
    <item android:drawable="@drawable/unselected_img"     android:state_selected="false" />
    <item android:drawable="@drawable/selected_img"     android:state_selected="true" />
</selector>

The three source image files (disabled_img.png, unselected_img.png, selected_img.png) are also located in the drawable folder.

Now back to your Java code. There is no need for the funky StateListDrawable garbage for many reasons. First, it just looks ugly, and is hard to maintain. But most importantly it goes against the principles of keeping your logic separate from your presentation. If you are managing your drawable resources in Java code, you know you are doing something fundametally wrong.

Here is what I am proposing instead. Whenever you want your button to be selected, you just pop this one-liner in there:

((LinearLayout)findViewById(R.id.button)).setSelected(true);

Or whenever you want the button to be in the disabled state, here is another one-liner:

((ImageView)findViewById(R.id.background)).setEnabled(false);

Please notice that in this last example I am specifying the disabled state on the ImageView inside the LinearLayout. For some reason whenever you change the enabled / disabled state of the LinearLayout, the selector is not being triggered. It works fine when you do it on the ImageView instead.

Temperage
  • 711
  • 1
  • 8
  • 17
  • 1
    This is a non-answer. The question was how to set the statedrawables programmatically. You explained how to do it in XML which was already shown in the question. – Cross_ May 27 '14 at 23:27
0

I don't know how you are adding the StateListDrawable, since the code is not here. But be sure to be check the documentation and the adding the setState().

You can set the properties from the View, such as yourView.setEnabled(true)

I hope that helps

raukodraug
  • 11,519
  • 4
  • 35
  • 37
  • yourView.setEnabled(true); This code is to enable or disable. I want to set the my image for the enabled/ disabled state. – Mani Feb 24 '11 at 04:55
  • yeah, you need the `view` to be enabled or disabled in order to trigger the state for the image, they are linked. You can't and shouldn't need to show the enabled/disabled state of the image if the `view` is not experiencing that behavior. – raukodraug Feb 24 '11 at 14:43
  • He is asking how to use the array of states parameter to make his code work. – Tyler Zale Mar 14 '11 at 21:04
  • 1
    related question: http://stackoverflow.com/questions/4697528/replace-selector-images-programmatically – Tyler Zale Mar 14 '11 at 21:08