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
Disable Webview Touch Events in Android
Should I Use Getapplicationcontext or Activity.This in a Long Running Asynctask
When Using Alertdialog.Builder with Edittext, The Soft Keyboard Doesn't Pop
Android Getting Exact Scroll Position in Listview
On Android, How to Switch Activities Programmatically
What Is The Different Between Explicit and Implicit Activity Call in Android
How to Get Results from an Intentservice Back into an Activity
Exception When Opening Parse Push Notification
Listfragment Does Not Accept My Layout
Change Actionbarsherlock Background Color
How to Be Notified When a Snackbar Has Dismissed Itself
Android Active Link of Url in Textview
How to Get Current Process Name in Android
Sending Message to a Handler on a Dead Thread When Getting a Location from an Intentservice