Android: Tint using DrawableCompat
Asked Answered
W

8

52

I'm trying to tint an image prior to Android API level 21. I've successfully tinted items using:

<android:tint="@color/red"/>

However, I can't seem to figure out how to do this through code on an ImageView:

Drawable iconDrawable = this.mContext.getResources().getDrawable(R.drawable.somedrawable);
DrawableCompat.setTint(iconDrawable, this.mContext.getResources().getColor(R.color.red));
imageView.setImageDrawable(iconDrawable);

I've tried setting the TintMode but this seems to make no different. Am I using the v4 compatibility class DrawableCompat incorrectly?

Whithersoever answered 6/11, 2014 at 19:55 Comment(1)
I managed to get the effect I was looking for by applying a ColorFilter, using mode SRC_IN which I believe means it just multiplies the alpha channel by the color - which was what I wanted with the tint anyway: setColorFilter(this.mContext.getResources().getColor(R.color.red), PorterDuff.Mode.SRC_IN)Whithersoever
N
49

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.

Nuptial answered 17/6, 2015 at 0:40 Comment(2)
Not sure why someone downvoted this but this code is actually what AppCompat does under the hood for previous Android releases. The AppCompat API is quite verbose to do this so I don't think you gain much by using it compared to this code.Nuptial
DrawableComapat.setTint(_, _) had some problem while I tried it to perform multiple times in prelollipop. And I got this as solution. ThanksGrogshop
C
127

In case anyone needs to use DrawableCompat's tinting without affecting other drawables, here's how you do it with mutate():

Drawable drawable = getResources().getDrawable(R.drawable.some_drawable);
Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
wrappedDrawable = wrappedDrawable.mutate();
DrawableCompat.setTint(wrappedDrawable, getResources().getColor(R.color.white));

Which can be simplified to:

Drawable drawable = getResources().getDrawable(R.drawable.some_drawable);
drawable = DrawableCompat.wrap(drawable);
DrawableCompat.setTint(drawable.mutate(), getResources().getColor(R.color.white));
Cragsman answered 19/6, 2015 at 0:25 Comment(5)
mutate() is the correct way to do it. Without it, you're tinting the drawable globally for the whole app. To cite the javadoc: "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."Gaberlunzie
Works ok for 4.1 and above (API 16 and above). Tested with latest support library (23.1.1). The mutate is need in 4.1 at least. Works in 21 and 23.Josettejosey
If you need to use a selector, use DrawableCompat.setTintList() instead.Sibbie
if you use this, drawable tint is changed globally, without revert. One of lib i use, uses this code, and it changes my drawable's color globally in every Activity @Gaberlunzie you are wrongNarvik
getResources().getColor(int res) - is deprecated, to get a color with just a Context use ContextCompat.getColor(context, R.color.my_color);Delanty
B
55

Previously tinting was not supported by DrawableCompat. Starting from support library 22.1 you can do that, but you need do it in this way:

Drawable normalDrawable = getResources().getDrawable(R.drawable.drawable_to_tint);
Drawable wrapDrawable = DrawableCompat.wrap(normalDrawable);
DrawableCompat.setTint(wrapDrawable, getResources().getColor(R.color.colorPrimaryLight));
Breastplate answered 27/4, 2015 at 8:27 Comment(4)
I wonder if anyone can get this work with mutate() drawablePremaxilla
@Premaxilla I'm pretty sure you don't need it anymore, but I just posted an answer here using DrawableCompat's tinting with mutate(). Thought you'd like to check it out anyway :)Cragsman
This works on API level 23 and API level 19 but not on API level 21.Stephanistephania
I had to add a drawable.invalidateSelf() to get it working, but other than that, no problemsForego
N
49

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.

Nuptial answered 17/6, 2015 at 0:40 Comment(2)
Not sure why someone downvoted this but this code is actually what AppCompat does under the hood for previous Android releases. The AppCompat API is quite verbose to do this so I don't think you gain much by using it compared to this code.Nuptial
DrawableComapat.setTint(_, _) had some problem while I tried it to perform multiple times in prelollipop. And I got this as solution. ThanksGrogshop
F
30

The answers here are not working for pre-lollipop-devices (SupportLib 23.4.0) but I've posted a workaround which is working for API 17 and up: https://mcmap.net/q/353637/-drawablecompat-settint-not-working-on-api-19

The following code was tested and is working on APIs 17, 19, 21, 22, 23 and N Preview 3:

    // https://mcmap.net/q/341922/-android-tint-using-drawablecompat
    Drawable drawable = DrawableCompat.wrap(ContextCompat.getDrawable(context, R.drawable.vector));
    image.setImageDrawable(drawable);

    /*
     * need to use the filter | https://mcmap.net/q/341922/-android-tint-using-drawablecompat
     * (even if compat should use it for pre-API21-devices | https://mcmap.net/q/341922/-android-tint-using-drawablecompat)
     */
    int color = ContextCompat.getColor(context, R.color.yourcolor);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        DrawableCompat.setTint(drawable, color);

    } else {
        drawable.mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN);
    }
Footpace answered 25/5, 2016 at 10:24 Comment(2)
@Footpace Doesn't DrawableCompat already perform this check for us? Hence the name "...Compat". Documentation from the class: /** * Helper for accessing features in {@link android.graphics.drawable.Drawable} * introduced after API level 4 in a backwards compatible fashion. */Newsmagazine
@Newsmagazine as I mentioned in my other post (https://mcmap.net/q/353637/-drawablecompat-settint-not-working-on-api-19) I had difficulties to get it work on all APIs - even as the Compat-class claims to be the one-fits-all-solution.Footpace
C
6

If you look at the source code for DrawableCompat you will see that for any version < 21 the method does nothing.

The idea of DrawableCompat seems to be simply not crashing on old versions, rather than actually providing that functionality.

Coccus answered 7/1, 2015 at 4:38 Comment(3)
This is a huge limitation of the DrawableCompat approach and seems to hold for app compat versions > 22.1.Colossae
If you actually follow which class is used you'll see that DrawableWrapper implements tint with a color filter.Ruppert
@JakeWharton the question remains, can you post how to do it correctly, none of the answers on this post works completely, or are too vague.Afoul
D
3

With support library 22.1 you can use DrawableCompat to tint drawables.

DrawableCompat.wrap(Drawable) and setTint(), setTintList(), and setTintMode() will just work: no need to create and maintain separate drawables only to support multiple colors!

Dentifrice answered 22/4, 2015 at 6:55 Comment(0)
F
3

I'll share my solution here because it may save some time to somebody.

I had an ImageView with vector drawable used as its source drawable (actually, it was Support Vector Drawable from Android Support Library 23.3). So, first I've wrapped it like so:

mImageView.setImageDrawable(DrawableCompat.wrap(mImageView.getDrawable()));

And after that I tried to apply tint to it like so:

DrawableCompat.setTint(
    mImageView.getDrawable(),
    getResources().getColor(R.color.main_color)
);

No luck.

I tried to call mutate() on wrapped drawable, as well as on original drawable - still no luck. invalidate() called on mImageView did the trick.

Flop answered 24/3, 2016 at 17:8 Comment(1)
Thy didn't you use AppCompatImageView?Mitinger
H
0

to set a tint and a drawable to a view and make it backward compatible while supporting the current theme of context using kotlin and not checking for the current SDK version and avoid deprecated methods:

imageView.setImageDrawable(
            ContextCompat.getDrawable(context, R.drawable.somedrawable).apply {
                setTint(ContextCompat.getColor(context, R.color.red))
            })
Hankow answered 1/2, 2020 at 14:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.