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?

Hippopatimus
  • 531
  • 1
  • 4
  • 5
  • 2
    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) – Hippopatimus Nov 06 '14 at 20:10

8 Answers8

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));
Renan Ferrari
  • 2,449
  • 5
  • 21
  • 26
  • 24
    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." – Brett Jul 21 '15 at 20:02
  • 3
    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. – fpanizza Dec 22 '15 at 19:56
  • 3
    If you need to use a selector, use `DrawableCompat.setTintList()` instead. – Ionoclast Brigham Apr 25 '16 at 22:30
  • 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 @Brett you are wrong – Jemshit Sep 29 '16 at 09:39
  • 1
    `getResources().getColor(int res)` - is deprecated, to get a color with just a Context use `ContextCompat.getColor(context, R.color.my_color);` – Kirill Karmazin Sep 14 '18 at 11:26
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));
Eugen Pechanec
  • 37,669
  • 7
  • 103
  • 124
Simon K. Gerges
  • 3,097
  • 36
  • 34
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.

BladeCoder
  • 12,779
  • 3
  • 59
  • 51
  • 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. – BladeCoder Nov 17 '15 at 10:37
  • `DrawableComapat.setTint(_, _)` had some problem while I tried it to perform multiple times in prelollipop. And I got this as solution. Thanks – Ngima Sherpa May 30 '17 at 09:36
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://stackoverflow.com/a/37434219/2170109

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

    // https://stackoverflow.com/a/30928051/2170109
    Drawable drawable = DrawableCompat.wrap(ContextCompat.getDrawable(context, R.drawable.vector));
    image.setImageDrawable(drawable);

    /*
     * need to use the filter | https://stackoverflow.com/a/30880522/2170109
     * (even if compat should use it for pre-API21-devices | https://stackoverflow.com/a/27812472/2170109)
     */
    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);
    }
Community
  • 1
  • 1
hardysim
  • 2,756
  • 2
  • 25
  • 52
  • @hardysim 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. */ – superuserdo Sep 20 '16 at 16:07
  • @superuserdo as I mentioned in my other post (http://stackoverflow.com/a/37434219/2170109) I had difficulties to get it work on all APIs - even as the Compat-class claims to be the one-fits-all-solution. – hardysim Sep 21 '16 at 11:08
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.

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Michael Smith
  • 717
  • 7
  • 12
  • This is a huge limitation of the DrawableCompat approach and seems to hold for app compat versions > 22.1. – Jade Aug 05 '15 at 17:48
  • 11
    If you actually follow which class is used you'll see that `DrawableWrapper` implements tint with a color filter. – Jake Wharton Sep 26 '15 at 05:43
  • @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. – desgraci Oct 18 '17 at 20:44
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!

bugraoral
  • 2,630
  • 1
  • 21
  • 25
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.

Community
  • 1
  • 1
aga
  • 27,954
  • 13
  • 86
  • 121
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))
            })
Amin Keshavarzian
  • 3,646
  • 1
  • 37
  • 38