Android: Drawable <android:tint> alternative API < 21 - android

Having to support night and day mode for API < 21 is a bit exhausting.
Android API >= 21 has allowed the use of android:tint like the following:
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="#drawable/ic_status"
android:tint="#color/white" />
The previous made changing the tint color of the icon depending on the night mode very easy. However, it is not supported on API < 21. Which means I need two versions of every single item in my app (with different screen sizes). Is there any easier way??
This answer suggested creating a new style. But the events colorControlNormal and colorControlActivated are not what I'm looking for.
This one suggested using AppCompatImageView but I made my layouts with AppCompatButton and specified the drawableTop

Related

Theme attributes not working correctly with VectorDrawable

I work on application which supports multi themes, dark and light, with min sdk version 21.
I found out that it's possible to use theme attribute (e.g. ?attr/logo_color) inside VectorDrawable.
So for example, If I set theme attribute to fill color of desired path
<vector ...>
<path
android:pathData="..."
android:fillColor="?attr/logo_color"/>
</vector>
or set theme attribute to tint whole vector
<vector android:tint="?attr/logo_color">
...
</vector>
I run the app (light theme), it sets color correctly, but when I change theme Activity.setTheme() (light to dark), color is not changed. Color is always 'cached' to previous theme's color. Interesting is that this doesn't work on lollipop and marshmallow, however on Android 10 it changes correctly.
On the other hand hand if I set android:tint="" color inside ImageView
<ImageView
...
app:srcCompat="#drawable/ic_logo"
android:tint="?attr/logo_colo"/>
It works with all versions but it of course change color of whole drawable.
Is it any bug or is it possible to use theme attributes inside VectorDrawable on lower apis with run time theme change?
Was facing the same issue, what I did was the following things
In the build.gradle file (app level), set the following (Suggesting this point since you have not mentioned what is the minSdk you are supporting)
android {
defaultConfig {
vectorDrawables.useSupportLibrary = true
}
}
If you are setting such drawables to any kind of views during run time or dynamically then
Instead of this (which is now deprecated)
context.getResources().getDrawable(/*Your resource id*/)
Use this
ContextCompat.getDrawable(context, /*Your resource id*/)
Explanation: What the ContextCompat class does is make sure that the drawable you get is complying to whatever theme is currently being used in your app (Android 5.0+)
If you support even below that, then you can use a ContextThemeWrapper to wrap your current context and apply a specific theme to the drawable and then use it whenever you like
Good things to read
Jorge Castillo's Article about ContextThemeWrapper

Doesn't Android Tint support API level 19?

Layout code for
Button:
<Button
android:id="#+id/button1"
style="#style/OneButton"
android:layout_width="fill_parent"
android:drawableLeft="#drawable/ic_setting"
android:text="Setting"/>
Bitmap for drawable:
ic_setting:
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="#drawable/ic_redeem"
android:tint="#color/red"/>
This works fine in API level 21 and above.
Tint Drawable Resources
With Android 5.0 (API level 21) and above, you can tint bitmaps and
nine-patches defined as alpha masks. You can tint them with color
resources or theme attributes that resolve to color resources .
The AppCompat support library provides APIs that you can use to support pre-Lollipop devices (mainly DrawableCompat#wrap() and DrawableCompat#setTintList()). Check out Chris Banes' blog post for more information.

Selector/Ripple/Tint for ImageView

I have an ImageView with a Bitmap as as its source. I want to give a selector/ripple/tint to the ImageView. When I click on ImageView I want to show a white color (#AAFFFFFF) tint with alpha.
This is what I have tried.
selector_image.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#color/blue_a" android:state_pressed="true"/>
<item android:drawable="#color/green_a" android:state_selected="true"/>
<item android:drawable="#color/red_a" android:state_activated="true"/>
<item android:drawable="#android:color/transparent"/>
</selector>
Then I set this as the ImageView's background using android:background="#drawable/selector_image". However this changes the background which is behind my Bitmap, hence is not visible on top of the Bitmap.
Have a look at this video for help
Image Ripple Effect.
The Video have a ripple effect. I want this ripple in post-lollipop devices and a simple tint without ripple for pre-lollipop.
How can I achieve this effect?
For ripple effect affect you don't need a selector but a ripple like
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight">
<item android:id="#android:id/mask">
<shape android:shape="oval">
<solid android:color="?android:colorAccent" />
</shape>
</item>
</ripple>
and put this under drawable-v21 folder.For pre lollipop device create a a normal selector and set it as background.Also both the files should have the same name.
How about ImageButton instead of ImageView. You can easily set a background selector for different states.
First of all, to get the nice ripple effect on API 21+, and fallback to a light shadow on older platforms, make sure you are using appcompat-v7 support library, then you can use ?selectableItemBackground to get the effect.
Now the question is about how to add the effect on top of your ImageView. Here are some possible solutions.
If you are using ImageViews as items in a ListView, you can set the following attributes for ListView.
<ListView
...
android:drawSelectorOnTop="true"
android:listSelector="?selectableItemBackground" />
If you are using the ImageView inside a FrameLayout, you can set the ripple as foreground of the FrameLayout.
<FrameLayout
...
android:foreground="?selectableItemBackground" />
You can always add a wrapping FrameLayout over the ImageView to get the foreground ripple. Note that andriod:foreground is only added to all the views since API v23, so we cannot utilize that.
You can also extend ImageView to add backward-compatible foreground support to it. If you want to go this way, leave a comment and I'll write more about it here.
Edit:
Checkout my implmentation of a RelativeLayout with foreground: https://gist.github.com/DreaminginCodeZH/9067a68d6e836389933c
Drop the attrs.xml into res/values, and ForegroundRelativeLayout.java into your own package, then replace all occurences of RelativeLayout to ImageView.
Then you can reference the custom view in your XML. Instead of using ImageView, use the fully qualified name of your custom view (com.example.android.ForegroundImageView for example) as the tag name, and add android:foreground="?selectableItemBackground" to it. This way it should work properly now.
(It should work properly if you don't target API 23 which will enable foreground for all views on API 23 (otherwise it won't be enabled), resulting in two foregrounds - His code is written far before the release of API 23. If you target API 23, you can workaround this by adding some API level check yourself.)
I was also planning to release a library for android:foreground backward-compatibility. If I had the time :)
One more thing, solution #3 of wrapping the ImageView with a FrameLayout should always work (easily), without all the work required above. You need to leverage it with the slight performance loss yourself.

Lollipop's backgroundTint has no effect on a Button

I have a Button in my Activity, and I'd like it to have my theme's accent color.
Instead of making my own drawables like we had to do pre-Lollipop, naturally I'd like to use the new backgroundTint attribute.
<Button
android:id="#+id/btnAddCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="#color/accent"
android:text="#string/addressInfo_edit_addCode" />
Unfortunately it has no effect, the button stays gray.
I tried different values for backgroundTintMode, which didn't change anything.
I also tried doing it programmatically in my Activity, which didn't change anything.
addCodeView.findViewById(R.id.btnAddCode).setBackgroundTintList(
getResources().getColorStateList(R.color.accent));
Why is my tint ignored?
EDIT:
Just to clarify, I am indeed testing on a Lollipop device.
Other widgets (e.g. EditText) are correctly and automatically tinted.
The bad news
Like BoD says, it's meaningless to tint a Button's background in Lollipop 5.0 (API level 21).
The good news
Lollipop 5.1 (API level 22) seems to have fixed this by changing btn_mtrl_default_shape.xml (among other files): https://android.googlesource.com/platform/frameworks/base/+/6dfa60f33ca6018959ebff1efde82db7d2aed1e3%5E!/#F0
The great news
The new support library (version 22.1+) adds backward-compatible tinting support to lots of components, including AppCompatButton!
Unfortunately, the android:backgroundTint property still doesn't work (maybe I'm doing something wrong) -- so you have to set the ColorStateList in code, using setSupportBackgroundTintList(). It'd be really nice to see android:backgroundTint supported in the future. Update: Marcio Granzotto commented that app:backgroundTint works on AppCompatButton! Note that it's app:, not android:, because it's in the app/library.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<AppCompatButton
android:id="#+id/mybutton"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Testing, testing"
app:backgroundTint="#ff00ff"/>
</LinearLayout>
Your activity will automatically inflate an AppCompatButton instead of the normal Button if you let it inherit from AppCompatActivity.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AppCompatButton v = (AppCompatButton) findViewById(R.id.mybutton);
ColorStateList csl = new ColorStateList(new int[][]{new int[0]}, new int[]{0xffffcc00});
v.setSupportBackgroundTintList(csl);
}
}
You should of course get the ColorStateList from a color resource, but I was lazy, so...
Oh, and don't forget to base your app theme on one of the Theme.AppCompat themes, or the compat views will be very, very sad... ;)
This worked on both 2.3.7 (Gingerbread MR1) and 5.0 (Lollipop 'Classic').
It seems that tinting a ripple drawable is meaningless (and the default background of a button is a ripple drawable).
In fact, after looking at the platform's default button drawable, I found the "correct" way to do this:. You have to define this in your theme:
<item name="android:colorButtonNormal">#color/accent</item>
(Of course this is only for level 21+.)
Warning: since this is defined in a theme, this will use the given color for all the buttons (at least all of the buttons in activities using that theme.)
As a bonus, you can also change the ripple color by defining this:
<item name="android:colorControlHighlight">#color/accent_ripple</item>
To resolve issues related to tinting on Android 5.0.x I use something like this:
public static void setButtonTint(Button button, ColorStateList tint) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP && button instanceof AppCompatButton) {
((AppCompatButton) button).setSupportBackgroundTintList(tint);
} else {
ViewCompat.setBackgroundTintList(button, tint);
}
}
It uses the support method only for API 21 and the ViewCompat one for all other cases.
I usually do it dynamically by using PorterDuff:
mbutton = (Button) findViewById(R.id.mybutton);
mbutton.getBackground().setColorFilter(anycolor, PorterDuff.Mode.MULTIPLY);
You can check different blending modes here and nice examples here.
Just use app:backgroundTint instead of android:backgroundTint, the tint will take effect below Lollipop. The reason is AppCompatActivity use AppCompatViewInflater to auto change Button or TextView to AppCompatButton or AppCompatTextView, then app:backgroundTint take effect.
In my project I used it, it worked.
Tested on API 19 through API 27
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.AppCompatButton
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="#style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/retry"
android:textColor="#android:color/white"
app:backgroundTint="#android:color/holo_red_dark" />
produces output as -
I think you need to have android:background set to make android:backgroundTint work.
To be more accurate, my guess is that you can't backgroundTint the default button background from Material themes, which is defined as a RippleDrawable.
Just use app:backgroundTint instead of android:backgroundTint
Similar issue was reported on google https://code.google.com/p/android/issues/detail?id=201873
But after release of Android Support Library, revision 23.2.1 (March 2016) This bug is solved.
Issue : FloatingActionButton.setBackgroundTintList(#Nullable ColorStateList tint) no longer changes background color
update Support Library to Android Support Library to 23.2.1
Use design support library(23.2.1) and appcompatwidgets as below
Material Design for Pre-Lollipop Devices :
AppCompat (aka ActionBarCompat) started out as a backport of the
Android 4.0 ActionBar API for devices running on Gingerbread,
providing a common API layer on top of the backported implementation
and the framework implementation. AppCompat v21 delivers an API and
feature-set that is up-to-date with Android 5.0
Android Support Library 22.1 :
The ability to tint widgets automatically when using AppCompat is
incredibly helpful in keeping strong branding and consistency
throughout your app. This is done automatically when inflating layouts
- replacing Button with AppCompatButton, TextView with AppCompatTextView, etc. to ensure that each could support tinting. In
this release, those tint aware widgets are now publicly available,
allowing you to keep tinting support even if you need to subclass one
of the supported widgets.
If we look into the source code of Support Library, we see that it tints normally it's known buttons, but if we change the shape of our button (I have round button) tint doesn't work ok in api<=21.
We can also see that TintManager became public class (appcompat-v7:23.1.1), so we can take ColorStateList from default button shape (which is tinted ok in 5.0) for current theme (so we don't have to create the array of colors):
Context c = ...; // activity
AppCompatButton ab = ...; // your button
// works ok in 22+:
if (Build.VERSION.SDK_INT <= 21) {
// default appcompat button, that is tinted ok with current theme colors "abc_btn_default_mtrl_shape":
// ColorStateList tint = TintManager.get(c).getTintList(R.drawable.abc_btn_default_mtrl_shape);
// Appcompat 23.2 change:
ColorStateList tint = AppCompatDrawableManager.get().getTintList(c, R.drawable.abc_btn_default_mtrl_shape);
ab.setSupportBackgroundTintList(tint);
}
Because attribute backgroundTint is only used in API level 21 and higher
Be aware recyclerview most updated lib can cause this bug as well.
This command
sendBtnView.setBackgroundTintList(colorState)
worked perfectly in the past but stop working for me. after research it turns out the cause is the lib that was addeded to gradle dependencies:
compile 'com.android.support:recyclerview-v7:+'
So I tried to change it to 23.02.1 as it was recommended here in Amit Vaghela post.
I changed to
compile 'com.android.support:recyclerview-v7:23.02.1'
But gradle error said recyclerview lib does not have this version (23.02.1) (gradle could not find it in Jcenter raw.github or repo).
Then, becaouse I knew setBackgroundTintList command used to worke well in the past with version 22.02.0' in all the other libs I have in gradle dependencies.
so I change it to:
compile 'com.android.support:recyclerview-v7:22.02.0'
And now it works again.
I am not sure if this is recommended but you can try this:
Drawable newDrawable = mBtnAction.getBackground(); // obtain Bg drawable from the button as a new drawable
DrawableCompat.setTint(newDrawable, mActivity.getHomeTobBarBGColor()); //set it's tint
mBtnAction.setBackground(newDrawable); //apply back to button
In a general sense it works. Tried ViewCompat but it doesn't seem to work properly.
you can use backgroundTint <android.support.design.button.MaterialButton with "com.android.support:design:28.0.0-rc01" version
If you are using androidx then adding both prefixed and not prefixed version resolved issue on android 5.1:
<style name="Button_Primary">
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:backgroundTint">#color/button_primary_selector</item>
<item name="backgroundTint">#color/button_primary_selector</item><!--needed for android 5-->
</style>
button_primary selector is in color folder with following content:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto">
<item android:state_enabled="true" android:color="#android:color/holo_blue_dark" />
<item android:state_enabled="false" android:color="#android:color/darker_gray" />
</selector>
and apply it on regular button on AppCompatActivity
<Button style="#style/Button_Primary"/>
If you are developing an app that your target sdk is higher than api 21 and your minSdkVersion is 21(Lollipop) instead of
android:backgroundTint="#color/accent"
you can simply say..
android:background="#color/accent"

Button pressed color, how can I use default whether on 4.4 or previous?

My understanding is that 4.4 changed some of the highlight colors for buttons to be grey or more neutral rather than blue. In my app I have some buttons which are a custom drawable - just a shape with rounded corners and then a selector for all the states. Now in my app so far I have just hard coded actual colors in this selector which are the same as from the default Holo theme on 4.0-4.3. However, with this recent change I want the pressed state of these buttons to be default (grey) when ran on 4.4+ devices. This way they will match the action bar highlights etc.
So far I have tried to use attribute 'colorPressedHighlight' in my selector but this doesn't work (I don't fully understand attributes/styles to be honest). The app wont compile, seemingly you can't refer to an attribute in place of a color.
Is there a way to do this? I'm now thinking maybe a different selector for each API version and just hard code the values in each? From a brief look I think this is possible, seems very inelegant though. This really shouldn't be as hard as it seems, right?
Any help much appreciated.
res / values-v19
Create value folder for API level 19 i.e for android 4.4 and above.
res/values-v19/colors.xml
define custom color value for button selection color
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color
name="button_selection"
>hex_color(Gray)</color>
</resources>
Create value folder for API level below 19 i.e for below android 4.4
res/values/colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color
name="button_selection"
>hex_color(blue)</color>
</resources>
Then use this value in your custom drawable.

Categories

Resources