Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 286
Richard Le Mesurier Avatar asked Feb 25 '15 11:02

Richard Le Mesurier


3 Answers

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);
like image 58
mengoni Avatar answered Nov 19 '22 17:11

mengoni


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);
}
like image 6
Harry Sharma Avatar answered Nov 19 '22 16:11

Harry Sharma


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.

like image 3
InTwoMinds Avatar answered Nov 19 '22 15:11

InTwoMinds