Replace selector images programmatically
Asked Answered
A

2

116

I have an ImageView that has a drawable image resource set to a selector. How do I programmatically access the selector and change the images of the highlighted and non-highlighted state?

Here is a code of selector:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/iconSelector">
  <!-- pressed -->
  <item android:state_pressed="true" android:drawable="@drawable/btn_icon_hl" />
  <!-- focused -->
  <item android:state_focused="true" android:drawable="@drawable/btn_icon_hl" />
  <!-- default -->
  <item android:drawable="@drawable/btn_icon" />
</selector>

I want to be able to replace btn_icon_hl and btn_icon with other images.

Asymmetric answered 15/1, 2011 at 1:27 Comment(2)
wouldn't it be easier to have two selectors and swap them?Auric
Problem with that is can you end up with hundreds of xml files.Unexampled
T
246

As far as I've been able to find (I've tried doing something similar myself), there's no way to modify a single state after the StateListDrawable has already been defined. You can, however, define a NEW one through code:

StateListDrawable states = new StateListDrawable();
states.addState(new int[] {android.R.attr.state_pressed},
    getResources().getDrawable(R.drawable.pressed));
states.addState(new int[] {android.R.attr.state_focused},
    getResources().getDrawable(R.drawable.focused));
states.addState(new int[] { },
    getResources().getDrawable(R.drawable.normal));
imageView.setImageDrawable(states);

And you could just keep two of them on hand, or create a different one as you need it.

Twin answered 15/1, 2011 at 3:31 Comment(13)
I wasn't able to add this to an image view. setState is not available on it.Asymmetric
actually I found it, its setImageDrawable() Thank you very much it worked and saved me about 40 xml files!Asymmetric
So I have another related note to this. I was hoping you can answer. I have this selector set on the ImageView that is inside the Custom Control. Custom control also has a selector as a background. So the selector of the whole control works, while the ImageView selector does not. Is there something I am doing wrong? Is there a sequence?Asymmetric
You're welcome! Yeah I don't know why I put setState, should be setImageDrawable, you're right. As per your other question, I'd suggest posting a new question with the code for your custom control and its selector, I'm not sure on the answer to that.Twin
here is the question. #4651941Asymmetric
i am using the same code. always the image which i have specified in ----> new int[]{} state remains. where i was wrong ??Redolent
@Asymmetric , kcoppock : I'm trying to do something similar in a listview with alternating background colors, created a "darkBgStateList" and "lightBgStateList", however the same color is applied to all the even or odd views every time a even/odd is selected, as if they shared. Can't different views use the same selector ?Brandenburg
I imagine you're doing something in your "set selected" logic or your getView() logic that's settings all odds or evens. Best to start a new question for this.Twin
@Redolent Thats because you need to implement the onclick, else it sees it as a non pressable imageviewNadene
Be careful, the order in which you add the states is important!Birddog
cant we have this on a Button rather an ImageView?Paratuberculosis
Thank you so much. I was tired of creating so many drawables from xml. This snippet helps making a global method that I can call from anywhere. Btw you can make this with color too instead of drawable states.addState(new int[]{android.R.attr.state_activated}, ContextCompat.getDrawable(context,R.color.black));Algesia
Just for your information, the order of calling addState is extremely important.Scopula
U
6

I had the same problem and went a step further to solve it. The only problem however is you can not specify the NavStateListDrawable in xml, so you have to set the background of your UI element through code. The onStateChange method must then be overriden to ensure that every time the level of the main drawable is changed, that you also update the level of the child level list.

When constructing the NavStateListDrawable you have to pass in the level of the icon you wish to display.

public class NavStateListDrawable extends StateListDrawable {

    private int level;

    public NavStateListDrawable(Context context, int level) {

        this.level = level;
        //int stateChecked = android.R.attr.state_checked;
        int stateFocused = android.R.attr.state_focused;
        int statePressed = android.R.attr.state_pressed;
        int stateSelected = android.R.attr.state_selected;

        addState(new int[]{ stateSelected      }, context.getResources().getDrawable(R.drawable.nav_btn_pressed));
        addState(new int[]{ statePressed      }, context.getResources().getDrawable(R.drawable.nav_btn_selected));
        addState(new int[]{ stateFocused      }, context.getResources().getDrawable(R.drawable.nav_btn_focused));

        addState(new int[]{-stateFocused, -statePressed, -stateSelected}, context.getResources().getDrawable(R.drawable.nav_btn_default));


    }

    @Override
    protected boolean onStateChange(int[] stateSet) {

        boolean nowstate = super.onStateChange(stateSet);

        try{
            LayerDrawable defaultDrawable = (LayerDrawable)this.getCurrent();


            LevelListDrawable bar2 =  (LevelListDrawable)defaultDrawable.findDrawableByLayerId(R.id.nav_icons);
            bar2.setLevel(level);
        }catch(Exception exception)
        {

        }

        return nowstate;
    }
}

For all of the different navigation button drawable states i have something like the following.

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

   <item android:drawable="@drawable/top_bar_default" >

   </item>

    <item android:id="@+id/nav_icons" android:bottom="0dip">
        <level-list xmlns:android="http://schemas.android.com/apk/res/android">
            <item android:maxLevel="0" >
                <bitmap
                    android:src="@drawable/top_bar_icon_back"
                    android:gravity="center" />
            </item>
            <item android:maxLevel="1" >
                <bitmap
                    android:src="@drawable/top_bar_icon_nav"
                    android:gravity="center" />
            </item>
            <item android:maxLevel="2" >
                <bitmap
                    android:src="@drawable/top_bar_icon_settings"
                    android:gravity="center" />
            </item>
            <item android:maxLevel="3" >
                <bitmap
                    android:src="@drawable/top_bar_icon_search"
                    android:gravity="center" />
            </item>
        </level-list>

    </item>

</layer-list>

I was going to post this as a question and answer, but seeing as you've asked the very question, here you go. Note, this saves you a hell of a lot of xml file definitions. i went down from about 50-100 xml definitions down to about 4!.

Unexampled answered 15/1, 2011 at 4:38 Comment(5)
The navstatelistdrawable code effectively makes the selector xml redundant.Unexampled
Hi emilie, Is there a possibility that drawables as a button background wont show up the first time for any reason. Currently I am getting the problem where I need to touch the area of the button for the background to show up, or switch out and switch back to the activity. (This only happens on a hdpi screen, but works fine on my mdpi). I believe others may have this problem too. Is your code tested for all screen densities?Janijania
Hi, no, this was written a fair while back and was only for one device at the time. I'm not sure what kind of issue might arrise though as far as i'm aware multiple screen densities and layouts shouldn't present a problem.Unexampled
Thanks I don't quite know what I was doing wrong but in the end I just had the following : buttonStates = new StateListDrawable(); buttonStates.addState(new int[]{statePressed}, ApplicationConstants.moduleImageLoader.findImageByName(drawable_pressed)); buttonStates.addState(new int[]{-stateFocused, -statePressed, -stateSelected}, ApplicationConstants.moduleImageLoader.findImageByName(drawable_normal));Janijania
This is the first place where I see that negative values have to be used for states set to false. The documentation is not very clear about it. Thanks for the tip!Jural

© 2022 - 2024 — McMap. All rights reserved.