Android: Tint Using Drawablecompat

Android: Tint using DrawableCompat

The simplest way to tint cross-platform (if you don't need a ColorStateList) is:

drawable.mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN);

Don't forget to mutate the Drawable before applying the filter.

Android: Tint one of three icons

You have to mutate() your drawables.

Now you refer to the exact same source. As soon as you mutate your drawable, each one will have its own state.

Drawable d = ContextCompat.getDrawable(this, R.drawable.my_vector_drawable).mutate();

From docs:

Make this drawable mutable. This operation cannot be reversed. A mutable drawable is guaranteed to not share its state with any other drawable. This is especially useful when you need to modify properties of drawables loaded from resources. By default, all drawables instances loaded from the same resource share a common state; if you modify the state of one instance, all the other instances will receive the same modification. Calling this method on a mutable Drawable will have no effect.

DrawableCompat setTint tints all new Drawables with the same id

all you need is to mutate your drawable before setting the tint:

bubbleDrawable.mutate()

Drawable.mutate

Make this drawable mutable. This operation cannot be reversed. A
mutable drawable is guaranteed to not share its state with any other
drawable. This is especially useful when you need to modify properties
of drawables loaded from resources. By default, all drawables
instances loaded from the same resource share a common state; if you
modify the state of one instance, all the other instances will receive
the same modification. Calling this method on a mutable Drawable will
have no effect.

android imageview's tint set in xml overrides tint set on DrawableCompat programmatically

Use setColorFilter() method.

Drawable myIcon = getResources().getDrawable( R.drawable.button ); 
ColorFilter filter = new LightingColorFilter( Color.BLACK, Color.BLACK);
myIcon.setColorFilter(filter);

Edit: just improved the formatting...

How to set tint for an image view programmatically in android?

UPDATE:

@ADev has newer solution in his answer here, but his solution requires newer support library - 25.4.0 or above.


You can change the tint, quite easily in code via:

imageView.setColorFilter(Color.argb(255, 255, 255, 255)); // White Tint

If you want color tint then

imageView.setColorFilter(ContextCompat.getColor(context, R.color.COLOR_YOUR_COLOR), android.graphics.PorterDuff.Mode.MULTIPLY);

For Vector Drawable

imageView.setColorFilter(ContextCompat.getColor(context, R.color.COLOR_YOUR_COLOR), android.graphics.PorterDuff.Mode.SRC_IN);

DrawableCompat tinting does not work on pre-Lollipop

When you call wrap() then the original Drawable is wrapped internally into a new DrawableWrapper which is used to implement the tinting on older devices. So to make it work you have to set the returned Drawable back to the EditText:

final Drawable originalDrawable = editText.getBackground();
final Drawable wrappedDrawable = DrawableCompat.wrap(originalDrawable);
DrawableCompat.setTintList(wrappedDrawable, ColorStateList.valueOf(Color.RED));
editText.setBackground(wrappedDrawable);

Since version 23.2.0 of the support library you can also use setTint() instead of setTintList() to set just one tint color without having to create a ColorStateList.

DrawableCompat.setTint(wrappedDrawable, Color.RED);

If you want to ensure backwards compatibility beyond API level 16 you run into a little snag. setBackground() was added in API level 16 and you need to call setBackgroundDrawable() on devices before that. It's best to implement a helper method which does that for you:

public static void setBackground(View view, Drawable background) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.setBackground(background);
} else {
view.setBackgroundDrawable(background);
}
}

Using DrawableCompat class to apply a tintList

d = DrawableCompat.wrap(d); creates a new instance if it's not already DrawableWrapper so you tint this new instance but the original which is stored in the button remains the same.

The whole code would look something like this

Button b = (Button) findViewById(R.id.button);
Drawable d = b.getBackground();
d = DrawableCompat.wrap(d);
DrawableCompat.setTintList(d, getResources().getColorStateList(...));
b.setBackground(d); // or setBackgroundDrawable on older platforms

So yeah, I'd go with the second approach you described because it abstracts the hard work from you.

EDIT:

Just took a dive into appcompat code and found out that the AppCompatButton tints iself and not the drawable unlike Lollipop native (but only if the background is on the whitelist, e.g. default appcompat button drawable). So you have to clear tint from the button itself first.

Button b = (Button) findViewById(R.id.button);

if (b instanceof AppCompatButton) {
((AppCompatButton)b).setSupportBackgroundTintList(null);
}

Drawable d = b.getBackground();
d = DrawableCompat.wrap(d);
DrawableCompat.setTintList(d, getResources().getColorStateList(...));
b.setBackground(d); // or setBackgroundDrawable on older platforms

EDIT 2:

The above code will throw a NullPointerException when you try to reset the button's tint list. I'm currently filing a bug report.

In the meantime I suggest you inflate the button with a custom background (non-whitelisted for tinting by appcompat) directly or with @null background and resolving the default button background by

TypedArray ta = context.obtainStyledAttributes(null, new int[]{android.R.attr.background}, R.attr.buttonStyle, R.style.Widget_AppCompat_Button);
Drawable d = ta.getDrawable(0);
ta.recycle();

Final solution

So as all this looks pretty fugly, the easiest (and only working and foolproof, yet hidious as well) solution for you now is this:

Button b = (Button) findViewById(R.id.button);
ColorStateList c = getResources().getColorStateList(...);
Drawable d = b.getBackground();
if (b instanceof AppCompatButton) {
// appcompat button replaces tint of its drawable background
((AppCompatButton)b).setSupportBackgroundTintList(c);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// Lollipop button replaces tint of its drawable background
// however it is not equal to d.setTintList(c)
b.setBackgroundTintList(c);
} else {
// this should only happen if
// * manually creating a Button instead of AppCompatButton
// * LayoutInflater did not translate a Button to AppCompatButton
d = DrawableCompat.wrap(d);
DrawableCompat.setTintList(d, c);
b.setBackgroundDrawable(d);
}

You should put this monstrosity away in a utility class.



Related Topics



Leave a reply



Submit