Android: 为了使 StateListDrawable 具有过滤器,克隆一个可绘制的文件

我试图使一个通用的框架函数,使任何可绘制成为突出显示时,按[聚焦][选择][等]

我的函数接受一个 Drawable 并返回一个 StateListDrawable,其中默认状态是 Drawable 本身,而 android.R.attr.state_pressed的状态是相同的 Drawable,只是使用了一个使用 setColorFilter的过滤器。

我的问题是,我不能克隆这个可绘制的图形,并使用应用的过滤器创建一个单独的实例。以下是我试图实现的目标:

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

有什么想法吗?

更新:

这个被接受的答案确实克隆了一个可画的东西。但是它并没有帮助我,因为我的一般功能在另一个问题上失败了。似乎当您向 StateList 添加可绘制内容时,它会丢失所有的过滤器。

29513 次浏览

Try the following:

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

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.

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()

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));

This is what works for me.

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

in case getConstantState() returns null.

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();
}