If the following TextView, tinted_tv, is tinted with SetTintList() (option 1), the property BackgroundTintList remains null (even when evaluated in a UI posted runnable).
However, when tinted with BackgroundTintList (setBackgroundTintList()) (option 2), it (getBackgroundTintList()) does not.
Both options work as expected, so I'm not sure what the significance of their difference is, or the better one to use?
Layout
<TextView
android:id="#+id/tinted_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white"
/>
Code
TextView tv = v.FindViewById<TextView>(Resource.Id.tinted_tv);
// option 1
tv.Background.SetTintList(Context.Resources.GetColorStateList(Resource.Color.color_state_list));
// option 2
tv.BackgroundTintList = Context.Resources.GetColorStateList(Resource.Color.color_state_list);
According to the Android.Views.View.BackgroundTintList Property documentation
Get method documentation[Android Documentation]
Return the tint applied to the background drawable, if specified.
Set method documentation[Android Documentation]
Applies a tint to the background drawable. Does not modify the current tint mode, which is PorterDuff+Mode by default. Subsequent calls to View.Background will automatically mutate the drawable and apply the specified tint and tint mode using Drawable.SetTintList(ColorStateList).
I would imagine that the Xamarin BackgroundTintList property getter/setter corresponds to Android's get/setBackgroundTintList() View methods. Does "raw" Android exhibit this same behavior (getBackgroundTintList() returns null following a call to setBackgroundTintList())?
View.BackgroundTintList is managed by the View and the tint is applied to the View.Background drawable when you call it. It's a layer of abstraction.
When you work directly with Drawable.TintList nobody knows or cares about it. That's why View.BackgroundTintList value remains unaffected.
View.BackgroundTintList has no precedence over View.Background.SetTintList. Whichever you call last wins.
On second look, there is one difference best described by a snippet from View source:
private void applyBackgroundTint() {
if (mBackground != null && mBackgroundTint != null) {
final TintInfo tintInfo = mBackgroundTint;
if (tintInfo.mHasTintList || tintInfo.mHasTintMode) {
mBackground = mBackground.mutate();
if (tintInfo.mHasTintList) {
mBackground.setTintList(tintInfo.mTintList);
}
if (tintInfo.mHasTintMode) {
mBackground.setTintMode(tintInfo.mTintMode);
}
// The drawable (or one of its children) may not have been
// stateful before applying the tint, so let's try again.
if (mBackground.isStateful()) {
mBackground.setState(getDrawableState());
}
}
}
}
Background drawable state is updated to state of view when you set View.BackgroundTintList, which is why you should choose this method when working with views.
When view state changes later background drawable state is updated regardless of which method you chose.
On top of View.BackgroundTintList there's also View.BackgroundTintMode which mirrors drawable API. Both of these values can be set by attributes in layout XML, something you can't do with standalone drawables. This approach may actually be used by the platform widgets.
Related
I am creating Android Instrumented tests using Espresso and I would like to test that the backgroundTint of a view changes after a certain action. I did not have any luck finding a similar question specifically for background tint. In this case it is an ImageView that uses a circle drawable and the color changes from green to red depending on server connection. The view is updating via livedata databinding
<ImageView
android:id="#+id/connected_status"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_gravity="end|top"
android:background="#drawable/circle"
android:backgroundTint="#{safeUnbox(viewModel.onlineStatus) ? #colorStateList/colorGreenMaterial : #colorStateList/colorRedPrimary}"
android:contentDescription="#string/connection_indication"
/>
How can I programmatically get the backgroundTint of the ImageView during an Instrumented test and check its color?
I believe I found a solution to this problem as the test is passing, however I do not know if it is the best solution. I noticed when getting the backgroundTintList from the ImageView it contains an array of integers representing colors. I was able to use this to test the colors like so (Kotlin):
// Get the integer value of the colors to test
val redColorInt = Color.parseColor(activityTestRule.activity.getString(R.color.colorRedPrimary))
var greenColorInt = Color.parseColor(activityTestRule.activity.getString(R.color.colorGreenMaterial))
// Add integers to a stateSet array
val stateSet = intArrayOf(redColorInt, greenColorInt)
// Get the view
val connectedStatus = activityTestRule.activity.findViewById<ImageView>(id.connected_status)
// Get the backgroundTintList
var tintList = connectedStatus.backgroundTintList
// Assert color, getColorForState returns 1 as default to fail the test if the correct color is not found
assertThat(tintList!!.getColorForState(stateSet, 1), `is`(redColorInt))
//Perform actions that change the background tint
...
// Get the updated backgroundTintList
tintList = connectedStatus.backgroundTintList
// Assert new color is now set
assertThat(tintList!!.getColorForState(stateSet, 1), `is`(greenColorInt))
I have a custom layout that has custom attributes, one of them being a color. I have users set this attribute to a color (not a common color) and I use TypedArray's getColor method to retrieve this color and set it to an integer (if I print this int out, it is negative). Let's say I do something like this:
int myColor;
TypedArray ta = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.MyView, 0, 0);
myColor = ta.getColor(R.styleable.MyView_myColor, -1);
if (myColor == R.color.special_shade_of_yellow) {
mySpecialMethod()
}
Now let's say a user sets the attribute to be R.color.special_shade_of_yellow. However, the if block never goes through so mySpecialMethod() never gets called. For some reason, myColor is a negative value, while R.color.special_shade_of_yellow isn't. Why aren't they returning the same values? Thanks!
Colors in Android can be slightly confusing. You have color resource identifiers (like R.color.my_color) and you have color values (like 0xff0000), but both are represented by an int value.
TypedArray.getColor() will return a color value, i.e. a real color that you can directly apply to a view. Therefore, it's not something you'll want to compare to R.color.special_shade_of_yellow with a simple ==.
Try this instead:
if (myColor == ContextCompat.getColor(getContext(), R.color.special_shade_of_yellow)) {
...
}
ContextCompat.getColor() will resolve your color resource identifier (here R.color.special_shade_of_yellow) to a color value, and then you can perform an == comparison.
How do you get the actual value of a referenced color. In a layout I can use the following...
android:textColor="?android:attr/colorAccent"
..and this works in setting the text color of a TextView to the theme defined accent color. How do I get the value of the colorAccent using code at runtime?
Also, how do you discover a list of all the available values, there must be a long list of available colors I could get hold of, but where is that list defined?
If the resource is an Android defined one:
var id = Android.Resource.Attribute.ColorAccent;
If the resource is within a Dialog, Widget, etc.. that is not an Android system resource (i.e. to obtain a DatePickerDialog resource)
var id = SomeDatePickerDialog.Resources.GetIdentifier("date_picker_header_date", "id", "android");
Using the id obtained:
var typedArray = Theme.ObtainStyledAttributes(new int[] { id });
var color = typedArray.GetColor(0, int.MaxValue);
if (color != int.MaxValue)
{
Log.Debug("COLOR", color.ToString());
}
The R list changes with API/Theme, for the base values available:
Colors: https://developer.android.com/reference/android/R.color.html
Styles: https://developer.android.com/reference/android/R.style.html
etc...
But for a complete reference you have to use the Android source for the API the you are looking at:
https://android.googlesource.com/platform/frameworks/base/
So the colors that are defined in the Oreo beta:
https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r4/core/res/res/color/
Then look within the specific color xml file for the how it is defined and use that definition to find the actual value of it (in one on the valueXXX files....)
For the example you have you can get that value with something like this:
//default color instead the attribute is not set.
var color = Color.Blue;
var attributes = new int[] { Android.Resource.Attribute.ColorAccent };
var typeArray = ObtainStyledAttributes(attributes);
//get the fist item (we are sending only one) and passing
//the default value we want, just in case.
var colorAccent = typeArray.GetColor(0, color);
colorAccent will have the Color set in your Theme for the ColorAccent attribute if any or the default value .
Important to mention that this method ObtainStyledAttributes is part of a Context so if you are already in an Activity you will find it as part of it but if you are in any other class you will need to pass in the context in case it's not available.
For the full list of available values you can get it from the Android.Resource.Attribute class. In VS do an inspection to see the different properties this class has. Maybe Android documentation has a better way though.
Hope this helps.-
I am trying to use drawable tinting using the following code in my RecyclerView
Drawable likeDrawable = ContextCompat.getDrawable(getActivity(), R.drawable.ic_thumb_up);
Drawable likeWrappedDrawable = DrawableCompat.wrap(likeDrawable);
DrawableCompat.setTint(likeWrappedDrawable,ContextCompat.getColor(getActivity(), android.R.color.white));
holder.ivLike.setImageDrawable(likeWrappedDrawable);
Now all of this is being done in the onBindViewHolder of the RecyclerView Adapter
I change this tint between three colors based on the state of that list item. This works all fine for Lolipop and above but below this version, the color of the list item is unpredictable. Sometimes it shows the right color but on refreshing the list sometimes it changes to some other color.
Anything im doing wrong here or the tinting thing on pre-lollipop is still unusable in this particular case?
Update
Including the code from my onBindViewHolder
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Drawable likeDrawable =
ContextCompat.getDrawable(getActivity(), R.drawable.ic_thumb_up);
Drawable likeWrappedDrawable = DrawableCompat.wrap(likeDrawable);
holder.tvLikeCount.setTextColor(ResUtil.getColor(R.color.light_font,
getActivity()));
DrawableCompat.setTint(likeWrappedDrawable,
ContextCompat.getColor(getActivity(), android.R.color.white));
if (tweetModel.isFavorited()) {
DrawableCompat.setTint(likeWrappedDrawable,
ContextCompat.getColor(getActivity(), android.R.color.holo_blue_light));
}
holder.ivLike.setImageDrawable(likeWrappedDrawable);
}
Call mutate() on the Drawable before calling setTint()
Drawable likeDrawable = ContextCompat.getDrawable(getActivity(), R.drawable.ic_thumb_up).mutate();
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.
http://developer.android.com/reference/android/graphics/drawable/Drawable.html#mutate()
TL;DR. If you're worried about efficiency, use TintedIconCache, a single class you can grab from this gist.
TintedIconCache cache = TintedIconCache.getInstance();
Drawable myTintedDrawable = cache.fetchTintedIcon(context, R.drawable.icon_01, R.color.color_01));
The need to do more than just .mutate()
As #Vladimir stated, you have to call drawable.mutate() to get an isolated state, otherwise any change made to your drawable properties would be reflected on all the other drawables sharing the same state.
This behavior is actually meant for good reasons, one of which is memory efficiency, and there are some scenarios where you'd want to keep that efficiency.
For instance, you might have a RecyclerView with items using the same drawable but tinted differently for each item according to some property (e.g. success, failure, warning, info, etc). If you're in that case, mutating your drawable for each item is not the best solution. You could think of creating all the possible drawables, with all the possible tints, but you might be creating useless onces. If you're in that case, I got you covered!
Enters TintedIconCache
A better solution is have some sort of drawable cache, where each uniquely tinted drawable is created only when needed, and then is cached efficiently for subsequent needs until you no longer need it or the system wants that occupied memory back.
I have already implemented this, in TintedIconCache. It's straightforward to use:
// Get an instance
TintedIconCache cache = TintedIconCache.getInstance();
// Will be fetched from the resources
Drawable backIcon = cache.fetchTintedIcon(context, R.drawable.icon, R.color.black));
// Will be fetched from the resources as well
Drawable bleuIcon = cache.fetchTintedIcon(context, R.drawable.icon, R.color.bleu));
// Will be fetched from the cache!!!
Drawable backIconTwo = cache.fetchTintedIcon(context, R.drawable.icon, R.color.back));
Consider looking at the gist to see how it works under the hood.
I have a bunch of code in a routine that looks a bit like this:
a.setContentView(R.layout.myLayout);
textview t1 = (TextView) a.findViewById(R.id.mylayout_t1);
t1.setText("Hello")
t1.setTypeface(font);
t1.setTextColor(colour);
t1.setTextSize(fontSize);
textview t2 = (TextView) a.findViewById(R.id.mylayout_t2);
t2.setText("Hello Again")
t2.setTypeface(font);
t2.setTextColor(colour);
t2.setTextSize(fontSize);
The problem I'm having is that before when the routine is called, the layout is done with all the fonts at the default font/size/colour and then they quickly change to the specified values, which is not very pleasant on the eye.
Is there some kind of command I can add to the beginning of the routine to suspend any layout, and then another command to resume at the end of the routine?
There are two ways:
1) Put your all code (you mentioned above) in onCreate() method and at last call t1.setVisible(true);
2) Put your code in the method in which you are creating your UI (like initUI() or something like that) and call this method before setting visibility to true.
Have you considered using XML to set the text style instead of doing it programmaticly. See this Android Dve Guide page for more on this topic.
Another (bad?) way might be to use XML to set the views visibility to false and when you have made your style changes, call t1.setVisibility(true). Haven't tried this one, so it might produce a similar, unwanted result.