setColorFilter() broken on Android 4, working on Android 5

岁酱吖の 提交于 2019-11-29 03:31:25

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!