DrawableCompat tinting does not work on pre-Lollipop
Asked Answered
B

1

38

I'm using the new TextInputLayout to wrap an EditText. When I determine a field has an error I do the following:

Drawable drawable = DrawableCompat.wrap(getEditText().getBackground());

DrawableCompat.setTintList(drawable, ColorStateList.valueOf(Color.RED));

This works on 5.0 and turns the underline red, but does nothing on 4.4 or 4.1 test devices. What am I missing here? Seems so simple and according to google "just works"... pretty sure I have the latest version of it as well:

compile 'com.android.support:design:22.2.0'

FWIW, if I do setColorFilter instead of setTint then it works on all platforms but then I have issues with it going away and not coming back as soon as the focus is set/left/etc... I'd prefer to do it with tint (and really prefer to have the tint apply to the focus and non-focus states if anybody is looking for extra credit lol)

Thanks!

Berryberryhill answered 16/6, 2015 at 15:35 Comment(14)
Normally those Compat classes don't do anything. They look at the API level and is the relevant feature is supported then they do it, if it is not they simply do nothing. Just look at the source code yourself and find out. I would take a look myself, but I am on my phone right now.Abran
In other words exactly the behavior you are experiencing. The only real feature of Compat class is that you can use them without having to worry about compatability. All that happens if something is not supported is nothing. Which of course can not be said for the real thing.Abran
That's not what is claimed here though. DrawableCompat is specifically said to bringing tinting to API levels 4 and later. chris.banes.me/2015/04/22/support-libraries-v22-1-0Berryberryhill
Yes I just looked at the source code, there are implementations for different API levels. Actually by looking at the source code I figured it out: The call to wrap() wraps the Drawable into a new DrawableWrapper. This DrawableWrapper is what is used to implement the tinting on older devices. A consequence of this - since DrawableWrapper is a new Drawable - is that you have to set the Drawable returned by wrap() back to the the EditText again afterwards. So just call editText.setBackground(drawable) and it should work.Abran
Ah, that makes some sense at least.... only problem is that editText.setBackground() is only API level 16 and beyond and I need 14+. Any thoughts on that? EDIT: more sillyness from google: #11948103Berryberryhill
You can use setBackgroundDrawable() before API level 16 and setBackground() from API level 16 and upwards. Its best to implement a helper method which checks the current API level and uses the appropriate call.Abran
I see you already figured that out. I wouldn't call it silliness, I think it is completely normal for an API to be modified over time. That's exactly what what @Deprecated is for. How else would you make changes unless for a time supporting both alternatives. Until the majority of all Android devices runs API level 16 or above (which is actually currently at 89% so it basically has already happened) we just have to live with things like this to ensure backwards compatibility.Abran
I guess it's the sum total of my last 24 hours that has me finding google a bit "silly"... first working around a bug in TextInputLayout where the hint doesn't show on 5.0+ until the field is focused, then trying to overcome lack of documentation on DrawableCompat (thanks to you for nixing that one!), and now dealing with surpressing deprecation warnings on setBackground which simply calls setBackgroundDrawable internally. Now that I'm done venting if you'd like to submit your response I'd love to mark it as an answer. Thanks again!Berryberryhill
Oh and one more thing, on 4.1 and 4.4, the red tint works for focus and non-focus, it just gets thicker as I wanted. But on 5.0 when focused it gets rid of my red tint and goes to the default green. Any thoughts on that?Berryberryhill
I can't really answer that question without more information about the Drawable you are using as background for the EditText. There probably is a separate green Drawable specified for the focused state.Abran
It's just the default drawable returned by EditText.getBackground(), then run through DrawableCompat.wrap(), then set as the background. Then when I want the error to go away I reverse the steps - get the background, unwrap it, and re-set it.Berryberryhill
The default background probably has the separate focused state set. Try using a custom Drawable as background.Abran
I haven't tried a different drawable, but doing what you suggested does in fact tint to red on earlier APIs, so that was the key thing I was missing. I'm hitting some odd issues (on 5.0 only) when I drill down to a nested fragment and come back to this screen, it has edittexts that were not in error previously bu are now showing as red. It almost seems like there is some recycling or sharing of the drawables among multiple edittexts. Not sure just yet.Berryberryhill
Please check this answer.Maximalist
A
68

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);
    }
}
Abran answered 16/6, 2015 at 19:50 Comment(3)
@toobsco42 That is simply not true. I don't understand what you think wouldn't work on Lollipop or lower. The code in this answer works from API level 4 all the way up to 23.Abran
This is what I tried gist.github.com/lawloretienne/5703098d2d32ee92082a And only on API level 21 it didn't get tinted correctly.Consortium
It seems there is an invalidation bug that might be related to this, see: code.google.com/p/android/issues/detail?id=172067#c13Stanislaw

© 2022 - 2024 — McMap. All rights reserved.