问题
I am trying to flash different colours onto the screen at regular intervals (a few times per second).
To change the colours, I use Drawable.setColorFilter(int color, Mode mode)
on the background of my main view:
myView.getBackground().setColorFilter(Color.RED, PorterDuff.Mode.SRC);
For debugging purposes, I added another view that I change using View.setBackgroundColor(int color)
.
The problem is that the setColorFilter()
calls are working on Lollipop, but are broken on previous versions (specifically Nexus 7 v4.4.4, Galaxy Nexus v4.2.1).
I call the colour changing code inside a Runnable
that is triggered at regular intervals by a Handler
.
The handler is being called on all platforms (I can see the background changes due to the debugging setBackgroundColor()
calls).
Below is the colour cycling code:
Handler mHandler;
RunnableOnTick thisRunnable;
View vDebug;
View vBroken;
class RunnableOnTick implements Runnable
{
int backgroundColor;
@Override
public void run()
{
color = random.nextInt(2);
switch (color)
{
case 0:
{
backgroundColor = Color.RED;
break;
}
case 1:
{
backgroundColor = Color.GREEN;
break;
}
}
// this works on all platforms
vDebug.setBackgroundColor(backgroundColor);
// this works only on Lollipop
vBroken.getBackground().setColorFilter(backgroundColor, PorterDuff.Mode.SRC);
vBroken.invalidate();
mHandler.postDelayed(thisRunnable, 100);
}
}
I have tried different PorterDuff.Mode
values - still can't get it working on Android 4.
What is different between Android v4 and v5 that would change the way setColorFilter()
works?
回答1:
Ultimately, it seems like the problem is that KitKat doesn't support using a ColorFilter (or implicitly an alpha) on a Drawable that will in turn be in a StateListDrawable. My solution was to use the same to code to construct the complex Drawable and then render that into a simple BitMapDrawable:
static Drawable createDrawable(Context context, int color, boolean disabled) {
OvalShape oShape = new OvalShape();
ShapeDrawable background = new ShapeDrawable(oShape);
background.getPaint().setColor(color);
ShapeDrawable shader = new ShapeDrawable(oShape);
shader.setShaderFactory(new ShapeDrawable.ShaderFactory() {
@Override
public Shader resize(int width, int height) {
return new LinearGradient(0, 0, 0, height,
new int[]{
Color.WHITE,
Color.GRAY,
Color.DKGRAY,
Color.BLACK
}, null, Shader.TileMode.REPEAT);
}
});
Drawable icon = ContextCompat.getDrawable(context, R.drawable.ic_chat_button).mutate();
icon.setColorFilter(context.getResources().getColor(R.color.control_tint_color), PorterDuff.Mode.SRC_IN);
Drawable layer = new LayerDrawable(new Drawable[]{ shader, background, icon });
layer.setAlpha(disabled ? 128 : 255);
// Note that on KitKat, setting a ColorFilter on a Drawable contained in a StateListDrawable
// apparently doesn't work, although it does on later versions, so we have to render the colored
// bitmap into a BitmapDrawable and then put that into the StateListDrawable
Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
layer.setBounds(0, 0, layer.getIntrinsicWidth(), layer.getIntrinsicHeight());
layer.draw(canvas);
return new BitmapDrawable(context.getResources(), bitmap);
}
回答2:
There's an issue in AppCompat with compound Drawable
below API 21 that me thinks is related:
https://code.google.com/p/android/issues/detail?id=191111
The simple solution is not using drawables from XML but create them in code and then apply setColorFilter
. That's why @Hardeep solution worked.
Fun trivia: In my case setColorFilter
on XML-created TextView
drawableLeft
worked fine, but only when invoked via click handler / delayed. When invoked in onCreate
/ onResume
etc nothing happened.
回答3:
I had the same issue on pre-lollipop, I solved replacing:
vBroken.getBackground().setColorFilter(backgroundColor, PorterDuff.Mode.SRC);
with:
Drawable d = vBroken.getBackground();
d.setColorFilter(backgroundColor, PorterDuff.Mode.MULTIPLY);
vBroken.setBackground(d);
回答4:
For me, a ColorFilter would not be applied to a color item in a StateListDrawable.
Creating a Drawable to represent that color and using that instead in the StateListDrawable saw setColorFilter working on pre-Lollipop devices I tested.
As a side note, I made a pure white Drawable so that tinting color would be applied at full opacity.
回答5:
Have you tried directly like this?!
vBroken.getBackground().setColorFilter(Color.argb(255, 255, 255, 255),PorterDuff.Mode.MULTIPLY));
回答6:
In order to paint drawable for as many different states as you want on all android versions, you can use this helper class:
public class MagicDrawable extends StateListDrawable {
private Map<Integer, Integer> stateColorsMap;
private int defaultColor;
public static MagicDrawable create(Drawable drawable, int defaultColor, int pressedColor, int selectedColor){
Map<Integer, Integer> map = new HashMap();
map.put(android.R.attr.state_selected, selectedColor);
map.put(android.R.attr.state_pressed, pressedColor);
MagicDrawable stateDrawable = new MagicDrawable(drawable, defaultColor, map);
return stateDrawable;
}
public static MagicDrawable createSelected(Drawable drawable, int defaultColor, int selectedColor){
Map<Integer, Integer> map = new HashMap();
map.put(android.R.attr.state_selected, selectedColor);
MagicDrawable stateDrawable = new MagicDrawable(drawable, defaultColor, map);
return stateDrawable;
}
public static MagicDrawable createPressed(Drawable drawable, int defaultColor, int pressedColor){
Map<Integer, Integer> map = new HashMap();
map.put(android.R.attr.state_pressed, pressedColor);
MagicDrawable stateDrawable = new MagicDrawable(drawable, defaultColor, map);
return stateDrawable;
}
/**
* Create state list drawable programmatically - just pass drawable, default color of drawable, and Map of (state , color)
* @param drawable resourse for icons
* @param defaultColor color for normal state
* @param stateColorsMap map of for ex. android.R.attr.state_pressed and ColorManager.someColor
*
* Map<Integer, Integer> map = new HashMap<>();
map.put(android.R.attr.state_pressed, ColorManager.rose);
*/
public MagicDrawable(Drawable drawable, int defaultColor, Map<Integer, Integer> stateColorsMap) {
super();
this.stateColorsMap = stateColorsMap;
this.defaultColor = defaultColor;
drawable.setColorFilter(defaultColor, PorterDuff.Mode.SRC_IN);
if (stateColorsMap != null) {
for (int state : stateColorsMap.keySet()) {
addState(new int[]{state}, drawable);
}
}
addState(new int[] {}, drawable);
}
@Override
protected boolean onStateChange(int[] states) {
if (stateColorsMap == null) {
super.setColorFilter(defaultColor, PorterDuff.Mode.SRC_IN);
return super.onStateChange(states);
}
boolean colorSet = false;
for (int state : states) {
for (int st : stateColorsMap.keySet()){
if (state == st) {
super.setColorFilter(stateColorsMap.get(st), PorterDuff.Mode.SRC_IN);
colorSet = true;
break;
}
}
}
if (!colorSet) {
super.setColorFilter(defaultColor, PorterDuff.Mode.SRC_IN);
}
return super.onStateChange(states);
}
@Override
public boolean isStateful() {
return true;
}
}
回答7:
/**
* Tint / Colorise the Supplied {@code drawable} into another color of supplied {@code colorResId}
* @param context
* @param drawable
* @param colorResId
* @return
*/
public Drawable tintThisDrawable(Context context ,Drawable drawable,@ColorRes int colorResId)
{
Resources res = context.getResources();
int color = res.getColor(colorResId);
if (drawable != null) {
drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
}
return drawable;
}
I remember this function working somewhere in my Project. Please test this yourself.
来源:https://stackoverflow.com/questions/28717831/setcolorfilter-broken-on-android-4-working-on-android-5