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

android, android-4.0-ice-cream-sandwich, android-5.0-lollipop

Solution

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

Problem

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?

Original source