Replace selector images programmatically

AndroidImageviewDrawable

Android Problem Overview


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.

Android Solutions


Solution 1 - Android

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.

Solution 2 - Android

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!.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiondropsOfJupiterView Question on Stackoverflow
Solution 1 - AndroidKevin CoppockView Answer on Stackoverflow
Solution 2 - AndroidEmileView Answer on Stackoverflow