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
Related
I am trying to create an app in android studio. I've only recently started to get interested in this and ran into a problem. As planned, for each move in the game, the player presses several buttons, I would like to put the id of these buttons in a separate list, and when necessary, use this list to change the color of all those buttons that are in this list
What i did:
I have the list->
val move_list: MutableList<Any> = mutableListOf()
When the player presses the button, I add its id to move_list
fun for_btn_buba2(view: View){
move_list.add(buba2.id)
In activity_main.xml my button seems like:
<Button
android:id="#+id/buba2"
android:text="Buba 2"
...
android:onClick="for_btn_buba2"/>
And on click of another button i wanted to insert code like this:
move_list[0].setBackgroundColor(Color.parseColor(colorString:"#FFFC9D45"))
move_list[0] means id for button buba2
In python it can be, but it isnt python)
How can I change the color of the button through the list index with the buttons?
Firstly I suggest you indicate the type of what is in the list, which is Int so like
val move_list: MutableList<Int> = mutableListOf()
Then you can probably do this in the onClick of the other button
findViewById<View>(move_list[0]).setBackgroundColor(Color.parseColor("#FFFC9D45"))
or to do it to all
move_list.forEach {
findViewById<View>(it).setBackgroundColor(Color.parseColor("#FFFC9D45"))
}
I also notice you directly refer to buba2 in it the for_btn_buba2. I have the feeling that you are writing this function for every buba. This is unnecessary. You can get the id from the view parameter because that is in fact the same id. So do
fun for_btn_buba(view: View){
move_list.add(view.id)
}
then you can give each buba the same for_btn_buba as android:onClick
Alternatively you don't even work with ids at all and make it a
val move_list: MutableList<View> = mutableListOf()
and then do
fun for_btn_buba(view: View){
move_list.add(view)
}
and then you can actually change the background like you wrote it:
move_list[0].setBackgroundColor(Color.parseColor("#FFFC9D45"))
or
move_list.forEach {
it.setBackgroundColor(Color.parseColor("#FFFC9D45"))
}
I have 2 edit texts in my view and I'm using data binding. What I want to achieve is whenever the first text view had 5 characters, the focus passes on to the next edit text, and whenever that edit text also had 5 characters, the focus should be removed from the whole view.
This is the code I wrote for my binding adapter:
#BindingAdapter("setMaxLength", "nextPart")
fun EditText.onTextChange(maxLength: Int, nextPart: EditText) {
filters = arrayOf<InputFilter>(InputFilter.LengthFilter(maxLength))
addOnTextChangeListener {
if (it.length == maxLength) {
clearFocus()
nextPart.requestFocus()
}
}
}
I don't know how I should pass these 2 arguments to my function in xml.
<androidx.appcompat.widget.AppCompatEditText
android:id="#+id/firstPart"
nextPart="alphabet"
setMaxLength="#{5}" />
<androidx.appcompat.widget.AppCompatEditText
android:id="#+id/alphabet"
setMaxLength="#{5}" />
This code has build issues with error:
Cannot find a setter for <androidx.appcompat.widget.AppCompatEditText setMaxLength> that accepts parameter type 'int'
If a binding adapter provides the setter, check that the adapter is annotated correctly and that the parameter type matches.
I'm not sure how I could do this, also addOnTextChangeListener is a text watcher and the functionality is tested.
Am I even on the right path here? Any ideas would be appreciated.
Try doing it like this
<androidx.appcompat.widget.AppCompatEditText
android:id="#+id/firstPart"
nextPart="#{alphabet}"
setMaxLength="#{5}" />
I've successfully made my first bind adapter and I wish to know a bit more about it.
I want to know how to make an attribute that can get only specific strings for a different state for my view.
For example every view has the visibility attribute that it can be"gone", "visible", "invisible"
<TextView
android:id="#+id/loading_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="#id/inventory_items_recycler"
app:layout_constraintEnd_toEndOf="#+id/inventory_items_recycler"
app:layout_constraintStart_toStartOf="#id/inventory_items_recycler"
app:layout_constraintBottom_toBottomOf="#id/inventory_items_recycler"
android:textSize="18sp"
android:visibility="gone"
app:item_id="#{ItemID.BLACK_GLOVES.ordinal()}"
/>
I've made a custom attribute called item_id that get a number that represent enum value. And in my binding utils I have this code:
#BindingAdapter("item_id")
public static void setItemName(TextView tv, int itemId) {
tv.setText(ItemData.get(ItemID.values()[itemId]).getName());
}
I prefer to have something similar to the visibility attribute that it value can be either "visible", "invisible" or "gone"
Bonus::
I wish android studio can auto-complete me for the possibilities that I can use.
You could pass directly the enum to your binding adapter, instead of converting it first to an int and than back to enum.
#BindingAdapter("item_id")
public static void setItemName(TextView tv, ItemID itemId) {
..
}
Then you could pass directly the enum in your xml:
app:item_id="#{ItemID.BLACK_GLOVES}"
This way you'll have a limited number of possibilities to enter and will be less likely to accidentally enter a meaningless integer.
However, binding adapters and custom attibutes are different. With a binding adapter, you still need to use the syntax of binding expression, ie: "#{ }".
android:visibility , on the other hand, is an attribute. You can also define custom attributes for your custom views and get something similar (have a limited number of input options and IDE shows you your options etc). But you shouldn't confuse that with binding adapters. These are two different concepts.
Try this:
#BindingAdapter("isGone")
#JvmStatic
fun View.setVisibility(isGone: Boolean) {
if (isGone) this.visibility = View.GONE else View.VISIBLE
}
Inside your xml:
<com.google.android.material.checkbox.MaterialCheckBox
android:id="#+id/cb_class"
style="#style/TextStyleNormal.White"
android:layout_marginStart="#dimen/margin_large"
isGone="#{isSharedDailyActivity}"// it take boolean value
app:buttonTint="#color/white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:useMaterialThemeColors="true" />
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 should be able to use a #BindingAdapter in Android dataBinding so that i can override a certain attribute. I am able to do it with a cusutom attribute but with a android built-in attribute how is it accomplished?
what i have so far:
in my viewModel i have a method that is annotated with #BindingAdapter and looks like this:
#BindingAdapter({"android:text"})
public static void setText(Button view,boolean language) {//i need to pass one more variable in here for area code , its just a integer, but how ?
if("french".equals(language))//i want to test if french && area code
view.setText("si vous play");
else if ("English".equals(language)) //i want to test if french && area code
view.setText("please");
}
but i have a few problems. Lets see the xml :
<Button
android:id="#+id/mybutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{`french,844`}"/>
See my issue, i want to pass in more then one parameter to dataBinding. But how ? Do i have to make a POJO object and pass it in ? even if i did that how do i set the object from xml ?
So if someone can just tell me the following i'll be fine:
1. how to pass multiple values to a bindingAdapter and
2. How to override a built-in android attribute in any view.
If you want to set two different values in your BindingAdapter, you should use two different attributes:
<Button
android:id="#+id/mybutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{`french`}"
app:areaCode="#{`844`}"/>
Then have two different attributes in your BindingAdapter:
#BindingAdapter({"android:text", "areaCode"})
public static void setText(Button view, String language, String areaCode) {
...
}
But it would probably be better to set a different "app:language" as that would be more clear to the developer.