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))
Related
I am using a TextInputLayoutHelper widget in order to follow the material guidelines for floating label inputs. It currently looks like this:
My code
In my activities onCreate function, I have:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val passwordInputLayout = this.findViewById<TextInputLayoutHelper>(R.id.input_layout_password)
passwordInputLayout.error = "8+ characters and at least one uppercase letter, a number, and a special character (\$, #, !)"
passwordInputLayout.isErrorEnabled = true
}
and my widget in my xml looks like...
<TextInputLayout
android:id="#+id/input_layout_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/EditTextTheme"
app:errorEnabled="true"
app:errorTextAppearance="#style/ErrorAppearance"
app:passwordToggleDrawable="#drawable/asl_password_visibility"
app:passwordToggleEnabled="true"
app:passwordToggleTint="?colorControlNormal">
<EditText
android:id="#+id/password_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="#string/set_a_password"
android:inputType="textPassword"
android:singleLine="true" />
</TextInputLayout>
What I want to do
I want to put an icon in the error/hint text (the exclamation triangle) to the right of the error text.
My Attempt
Attempt 1
I found an implementation which uses setError(text, drawable) but I am using Kotlin to setError is not available.
So I tried:
val warningIcon = ResourcesCompat.getDrawable(resources, R.drawable.ic_warning_black_24dp, null)
warningIcon?.setBounds(0, 0, warningIcon.intrinsicWidth, warningIcon.intrinsicHeight)
passwordInputLayout.error = "8+ characters and at least one uppercase letter, a number, and a special character (\$, #, !) $warningIcon"
but that does not render the drawable, only a string of the resource path.
Attempt 2
I found another one that overrides the TextInputLayoutHelper in order to set a drawable next to the text. As you can see, setError only contains the interface override fun setError(error: CharSequence?) which does not have a parameter for drawable.
override fun setError(error: CharSequence?) {
super.setError(error)
val warningIcon = ResourcesCompat.getDrawable(resources, R.drawable.ic_warning_black_24dp, null)
warningIcon?.setBounds(0, 0, warningIcon.intrinsicWidth, warningIcon.intrinsicHeight)
// mHelperView is a TextView used in my custom `TextInputLayout` override widget
mHelperView!!.setCompoundDrawables(null, null, warningIcon, null)
}
Is there an override or built in "Android way" to add this icon next to the error/hint text?
You can use SpannableString with ImageSpan like this
val errorDrawable = ContextCompat.getDrawable(context!!, R.drawable.ic_error)
your_text_input_layout.error = SpannableString("your string").apply {
setSpan(ImageSpan(errorDrawable, ImageSpan.ALIGN_BASELINE), 0, 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
This might help others who've fallen in to similar trouble. I looked at source codes. The thing is setError(text, drawable) has defined in EditText class not TextInputLayout as your layout is based on.
I have similar code and issue like you and since TextInputLayout doesn't have any method to replace Drawable then we have to create a TextView below TextInputLayout in order to simulate error message.
You want to place an icon inside the error container of TextInputLayout
If you check the real layout
You'll find that your textview is even wrap content.
Also in SDK sources
if (enabled) {
mErrorView = new AppCompatTextView(getContext());
mErrorView.setId(R.id.textinput_error);
...
So there is no place to insert an icon on the right AS IS.
One possible solution is to find this view programatically inside your layout, change width and apply drawable.
Second (and probably easiest) solution is to implement your own simple error layout and not to set error for TextInputLayout but manage the error state by yourself
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.
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 want to change background after clicking Button
var bm : Button = messeg
bm . setOnClickListener {
bm . background = R.color.green
}
Error Log:
Error:(35, 31) Type mismatch: inferred type is Int but Drawable! was
expected Error:Execution failed for task ':app:compileDebugKotlin'.
Compilation error. See log for more details
background requires a Drawable, but you are passing a color resource.
You can use setBackgroundColor to set a color resource:
bm.setBackgroundColor(R.color.green)
setBackgroundResource can be used to set a drawable resource:
bm.setBackgroundResource(R.drawable.green_resource)
background property can be used to set a drawable:
bm.background = ContextCompat.getDrawable(context, R.drawable.green_resource)
The current accepted answer is wrong for setBackgroundColor(). In the given example, you set the color to the resource id, but you must pass the color directly.
This won't fail because both values are int, but you'll get weird colors.
Instead of that, you should retrieve first the color from the resource, then set it as background. Example :
val colorValue = ContextCompat.getColor(context, R.color.green)
bm.setBackgroundColor(colorValue)