21

Android Studio version 2.1, gradle version 2.1.0, please correct me if you spot any misinterpretations :)

I am confused about support vectors in the support library 23.3.0. Specifically what I would like to do is tint an image button programmatically, whose src is defined is a vector drawable. From what I can tell this is not possible on pre-lollipop now.

I have read several related posts about the changes: 23.2.0 announcement and changes:

As of Android Support Library 23.3.0, support vector drawables can only be loaded via app:srcCompat or setImageResource().

Does the above mean that vector xmls can only be used pre-Lollipop via srcCompat or setImageResource(), and therefore cannot be dynamically tinted?

Here is my basic image button:

<ImageButton
    android:id="@+id/nav_header_exit_community_button"
    android:layout_width="48dp"
    android:layout_height="48dp"
    android:layout_alignParentTop="true"
    android:layout_alignParentRight="true"
    android:background="@null"/>

Works on Lollipop and above only:

    Drawable bg = ContextCompat.getDrawable(a, R.drawable.ic_exit_to_app_24dp);
    DrawableCompat.setTint(bg, headerTitleColor);
    exitButton.setImageDrawable(bg);

Attempting this pre-lollipop throws: android.content.res.Resources$NotFoundException: File res/drawable/ic_exit_to_app_24dp.xml from drawable resource ID #0x7f0200bf

Also works on Lollipop and above only

    Drawable bg = ContextCompat.getDrawable(a, R.drawable.ic_exit_to_app_24dp);
    DrawableCompat.setTint(bg, headerTitleColor);
    exitButton.setImageResource(R.drawable.ic_exit_to_app_24dp);

This throws the same error on pre-Lollipop.

However if I remove vectorDrawables.useSupportLibrary = true as pointed out by Ian Lake here, with the intent of having the build tools auto-generate pngs for pre-Lollipop devices, the pngs do not tint on pre-lollipop, so I'm back to square one.

I have also tried specifying the vector via srcCompat and retrieving it programmatically but I don't think I've been able to achieve that, even though it works on post-Lollipop if the vector is specified using src instead.

So the situation for 23.3.0 seems to be:

  • Post-Lollipop: src and srcCompat accept vectors, only src can be retrieved from the view as a drawable for tinting programmatically. Referencing vectors in code is possible using getDrawable, and they can be tinted.

  • Pre-Lollipop: srcCompat only can accept vectors, cannot be retrieved programmatically from the view for tinting. setImageResource can accept vectors, but only if vectorDrawables.useSupportLibrary = false, and tinting does not work. Similarly referencing vectors in code is not possible unless vectorDrawables.useSupportLibrary = false and tinting does not work.

Working on all versions using pngs:

   Drawable bg = ContextCompat.getDrawable(a, R.drawable.ic_nav_exit_community);
   DrawableCompat.setTint(bg, headerTitleColor);
   exitButton.setImageDrawable(bg);

Addendum:

This technique also works on post-Lollipop, but like the others on pre-Lollipop I get the drawable, but no tinting:

    Drawable bg = VectorDrawableCompat.create(a.getResources(), R.drawable.ic_exit_to_app_24dp, null);
    DrawableCompat.setTint(bg, headerTitleColor);
    exitButton.setImageDrawable(bg);

KIND OF SOLUTION:

Thanks to John's answer so far the only fool-proof way I can come up with to tint a support vector is to set a color filter on it - this means the DrawableCompat.setTint() function is seemingly not functional for me if the drawable in question is a support vector. I'm not sure if this is a legit bug, expected behavior or if I'm just doing something wrong!

Here is the solution I'm going with for the moment:

    Drawable bg;
    if(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        bg = VectorDrawableCompat.create(a.getResources(), R.drawable.ic_exit_to_app_24dp, null);
        exitButton.setColorFilter(headerTitleColor, PorterDuff.Mode.MULTIPLY);
    }
    else {
        bg = ContextCompat.getDrawable(a, R.drawable.ic_exit_to_app_24dp);
        DrawableCompat.setTint(bg, headerTitleColor);
    }
    exitButton.setImageDrawable(bg);
Community
  • 1
  • 1
Daniel Wilson
  • 18,838
  • 12
  • 85
  • 135
  • Does it work if you use the Vector Compat library to create your drawble? i.e VectorDrawableCompat.create(getResources(),R.drawable.ic_action_add,null) – Kuffs Apr 27 '16 at 18:29
  • I've updated my answer, looks like that 's yet another technique (!) to do the same kind of thing but tinting it doesn't work on pre-L – Daniel Wilson Apr 27 '16 at 21:03
  • it is not another techique, this is how `VectorDrawableCompat` should be created and if you read `DrawableCompat` docs you would see that [this](http://pastebin.com/CzEbnP9k) just works – pskink Apr 28 '16 at 06:07
  • Okay thank you! I would like to do more testing but it looks like the crucial missing component for me was the call to `wrap()` the drawable. This seems to be required for pre-L, along with the call to `create` rather than just loading the drawable or referencing it by it's view. I had actually tried those two ways with a wrap call, but not the `create`! If you want to post an answer telling me to wrap it I'll mark it :) – Daniel Wilson Apr 28 '16 at 06:46

3 Answers3

37

first of all you should use VectorDrawableCompat#create, once you have your Drawable you have to call DrawableCompat#wrap:

Potentially wrap drawable so that it may be used for tinting across the different API levels, via the tinting methods in this class.

so your code would look like this:

ImageView iv = ....
Drawable d = VectorDrawableCompat.create(getResources(), R.drawable.ic_exit_to_app_24dp, null);
d = DrawableCompat.wrap(d);
DrawableCompat.setTint(d, headerTitleColor);
iv.setImageDrawable(d);
pskink
  • 23,874
  • 6
  • 66
  • 77
  • i have use this same on another place it was working fine but this time its not working i don't why – Nouman Ch Aug 28 '18 at 06:10
  • ImageView imageView = new ImageView( new ContextThemeWrapper(parent.getContext(), R.style.CalendarCell_CalendarDate)); imageView.setDuplicateParentStateEnabled(true); parent.addView(imageView, new FrameLayout.LayoutParams(50, 50, CENTER_VERTICAL)); parent.setDayOfMonthTextView(imageView); – Nouman Ch Aug 28 '18 at 06:10
  • Drawable d = VectorDrawableCompat.create(getResources(), R.drawable.ic_circle, null); d = DrawableCompat.wrap(d); DrawableCompat.setTint(d, dailyLim.intValue()); cellView.getDayOfMonthImageView().setImageDrawable(d); – Nouman Ch Aug 28 '18 at 06:11
  • 1
    what is `dailyLim.intValue()`? for testing replace it with `Color.RED` – pskink Aug 28 '18 at 06:12
  • let me try this – Nouman Ch Aug 28 '18 at 06:12
  • working with Color.Red my dailyLim.intValue() is also working with setBackgroundColor but not with setTint. – Nouman Ch Aug 28 '18 at 06:17
  • yes worked but please guide me why it's not working with my dailyLim.intValue because this dailyLim.intValue is working in setBackgroundColor – Nouman Ch Aug 28 '18 at 06:22
  • i have no idea what do you have in it, tried to `Log.d` its value with `Integer.toHexString()`? – pskink Aug 28 '18 at 06:29
9

You can use setColorFilter method of ImageView:

imageView.setColorFilter(headerTitleColor, android.graphics.PorterDuff.Mode.MULTIPLY);

John
  • 1,304
  • 1
  • 9
  • 17
2

Another handy solution with Kotlin:

fun Context.drawableWithColor(@DrawableRes drawableRes: Int, @ColorInt color: Int): Drawable? {
    val pic = ContextCompat.getDrawable(this, drawableRes)
    pic?.setColorFilter(color, PorterDuff.Mode.SRC_IN)
    return pic
}

Use is as simple as:

val drawable = context.drawableWithColor(R.drawable.your_awesome_drawable, Color.BLUE)
Leo DroidCoder
  • 14,527
  • 4
  • 62
  • 54