Android: Cloning a drawable in order to make a StateListDrawable with filters

AndroidAndroid WidgetDrawable

Android Problem Overview


I'm trying to make a general framework function that makes any Drawable become highlighted when pressed/focused/selected/etc.

My function takes a Drawable and returns a StateListDrawable, where the default state is the Drawable itself, and the state for android.R.attr.state_pressed is the same drawable, just with a filter applied using setColorFilter.

My problem is that I can't clone the drawable and make a separate instance of it with the filter applied. Here is what I'm trying to achieve:

StateListDrawable makeHighlightable(Drawable drawable)
{
	StateListDrawable res = new StateListDrawable();
	
	Drawable clone = drawable.clone(); // how do I do this??
	
	clone.setColorFilter(0xFFFF0000, PorterDuff.Mode.MULTIPLY);
	res.addState(new int[] {android.R.attr.state_pressed}, clone);
	res.addState(new int[] { }, drawable);
	return res;
}

If I don't clone then the filter is obviously applied to both states. I tried playing with mutate() but it doesn't help..

Any ideas?

Update:

The accepted answer indeed clones a drawable. It didn't help me though because my general function fails on a different problem. It seems that when you add a drawable to a StateList, it loses all its filters.

Android Solutions


Solution 1 - Android

Try the following:

Drawable clone = drawable.getConstantState().newDrawable();

Solution 2 - Android

If you apply a filter / etc to a drawable created with getConstantState().newDrawable() then all instances of that drawable will be changed as well, since drawables use the constantState as a cache!

So if you color a circle using a color filter and a newDrawable(), you will change the color of all the circles.

If you want to make this drawable updatable without affecting other instances then, then you must mutate that existing constant state.

// To make a drawable use a separate constant state
drawable.mutate()

For a good explanation see:

http://www.curious-creature.org/2009/05/02/drawable-mutations/

http://developer.android.com/reference/android/graphics/drawable/Drawable.html#mutate()

Solution 3 - Android

This is what works for me.

Drawable clone = drawable.getConstantState().newDrawable().mutate();

Solution 4 - Android

This is my solution, based on this SO question.

The idea is that ImageView gets color filter when user touches it, and color filter is removed when user stops touching it. Only 1 drawable/bitmap is in memory, so no need to waste it. It works as it should.

class PressedEffectStateListDrawable extends StateListDrawable {

	private int selectionColor;

	public PressedEffectStateListDrawable(Drawable drawable, int selectionColor) {
		super();
		this.selectionColor = selectionColor;
		addState(new int[] { android.R.attr.state_pressed }, drawable);
		addState(new int[] {}, drawable);
	}

	@Override
	protected boolean onStateChange(int[] states) {
		boolean isStatePressedInArray = false;
		for (int state : states) {
			if (state == android.R.attr.state_pressed) {
				isStatePressedInArray = true;
			}
		}
		if (isStatePressedInArray) {
			super.setColorFilter(selectionColor, PorterDuff.Mode.MULTIPLY);
		} else {
			super.clearColorFilter();
		}
		return super.onStateChange(states);
	}

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

usage:

Drawable drawable = new FastBitmapDrawable(bm);
imageView.setImageDrawable(new PressedEffectStateListDrawable(drawable, 0xFF33b5e5));

Solution 5 - Android

I answered a related question here

Basically it seems like StateListDrawables indeed lose their filters. I created a new BitmapDrawale from a altered copy of the Bitmap I originally wanted to use.

Solution 6 - Android

Get clone drawable using newDrawable() but make sure it is mutable otherwise your clone effect gone, I used these few lines of code and it is working as expected. getConstantState() may be null as suggested by annotation, so handle this RunTimeException while you cloning drawable.

Drawable.ConstantState state = d.mutate().getConstantState();
if (state != null) {
    Drawable drawable = state.newDrawable().mutate();
}

Solution 7 - Android

Drawable clone = drawable.mutate().getConstantState().newDrawable().mutate();

in case getConstantState() returns null.

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
QuestiontalkolView Question on Stackoverflow
Solution 1 - AndroidFlavioView Answer on Stackoverflow
Solution 2 - AndroidPeter AjtaiView Answer on Stackoverflow
Solution 3 - AndroidYanru BiView Answer on Stackoverflow
Solution 4 - AndroidMalachiaszView Answer on Stackoverflow
Solution 5 - AndroidKunoView Answer on Stackoverflow
Solution 6 - AndroidKishan DongaView Answer on Stackoverflow
Solution 7 - AndroidMartin WangView Answer on Stackoverflow