Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error text in TextInputLayout is covered by keyboard

The TextInputLayout contains an EditText which in turn receives the input from the user. With TextInputLayout introduced with the Android Design Support Library we're supposed to set the error to the TextInputLayout holding the EditText rather than the EditText itself. When writing the UI will be focused on only the EditText and not the whole TextInputLayout which can lead to the keyboard covering the error. In the following GIF notice that the user has to remove the keyboard first to see the error message. This in combination with setting IME actions to move on using the keyboard leads to really confusing results.

example error

Layout xml code:

<android.support.design.widget.TextInputLayout     android:id="@+id/uid_text_input_layout"     android:layout_width="match_parent"     android:layout_height="wrap_content"     app:errorEnabled="true"     android:layout_marginTop="8dp">      <EditText         android:id="@+id/uid_edit_text"         android:layout_width="match_parent"         android:layout_height="wrap_content"         android:singleLine="true"         android:hint="Cardnumber"         android:imeOptions="actionDone"/>  </android.support.design.widget.TextInputLayout> 

Java code setting the error to the TextInputLayout:

uidTextInputLayout.setError("Incorrect cardnumber"); 

How can I make sure the error message is visible without the user acting to see it? Is it possible to move the focus?

like image 865
Franzaine Avatar asked Jun 25 '15 10:06

Franzaine


People also ask

How do I show error in TextInputLayout?

To display the error text, we'll have to call the method setError(String) on an instance of TextInputLayout in our MainActivity.

How do I get TextInputLayout from text?

TextInputLayout textInputCustomEndIcon = findViewById(R. id. editText); final TextInputEditText editText = new TextInputEditText(textInputCustomEndIcon. getContext()); textInputCustomEndIcon.


2 Answers

This is actually a known issue at Google.

https://issuetracker.google.com/issues/37051832

Their proposed solution is to create a custom TextInputEditText class

 class MyTextInputEditText : TextInputEditText {     @JvmOverloads     constructor(         context: Context,         attrs: AttributeSet? = null,         defStyleAttr: Int = android.R.attr.editTextStyle     ) : super(context, attrs, defStyleAttr) {     }      private val parentRect = Rect()      override fun getFocusedRect(rect: Rect?) {         super.getFocusedRect(rect)         rect?.let {             getMyParent().getFocusedRect(parentRect)             rect.bottom = parentRect.bottom         }     }      override fun getGlobalVisibleRect(rect: Rect?, globalOffset: Point?): Boolean {         val result = super.getGlobalVisibleRect(rect, globalOffset)         rect?.let {             getMyParent().getGlobalVisibleRect(parentRect, globalOffset)             rect.bottom = parentRect.bottom         }         return result     }      override fun requestRectangleOnScreen(rect: Rect?): Boolean {         val result = super.requestRectangleOnScreen(rect)         val parent = getMyParent()         // 10 is a random magic number to define a rectangle height.         parentRect.set(0, parent.height - 10, parent.right, parent.height)         parent.requestRectangleOnScreen(parentRect, true /*immediate*/)         return result;     }      private fun getMyParent(): View {         var myParent: ViewParent? = parent;         while (!(myParent is TextInputLayout) && myParent != null) {             myParent = myParent.parent         }         return if (myParent == null) this else myParent as View     } }``` 
like image 21
elFonzo Avatar answered Sep 28 '22 20:09

elFonzo


To make sure the error message is visible without the user acting to see it, I subclassed TextInputLayout and placed it inside a ScrollView. This lets me scroll down if needed to reveal the error message, on every occasion the error message is set. There are no changes needed in the activity/fragment class that uses it.

enter image description here

import androidx.core.view.postDelayed  /**  * [TextInputLayout] subclass that handles error messages properly.  */ class SmartTextInputLayout @JvmOverloads constructor(         context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : TextInputLayout(context, attrs, defStyleAttr) {      private val scrollView by lazy(LazyThreadSafetyMode.NONE) {         findParentOfType<ScrollView>() ?: findParentOfType<NestedScrollView>()     }      private fun scrollIfNeeded() {         // Wait a bit (like 10 frames) for other UI changes to happen         scrollView?.postDelayed(160) {             scrollView?.scrollDownTo(this)         }     }      override fun setError(value: CharSequence?) {         val changed = error != value          super.setError(value)          // work around https://stackoverflow.com/q/34242902/1916449         if (value == null) isErrorEnabled = false          // work around https://stackoverflow.com/q/31047449/1916449         if (changed) scrollIfNeeded()     } } 

Here are the helper methods:

/**  * Find the closest ancestor of the given type.  */ inline fun <reified T> View.findParentOfType(): T? {     var p = parent     while (p != null && p !is T) p = p.parent     return p as T? }  /**  * Scroll down the minimum needed amount to show [descendant] in full. More  * precisely, reveal its bottom.  */ fun ViewGroup.scrollDownTo(descendant: View) {     // Could use smoothScrollBy, but it sometimes over-scrolled a lot     howFarDownIs(descendant)?.let { scrollBy(0, it) } }  /**  * Calculate how many pixels below the visible portion of this [ViewGroup] is the  * bottom of [descendant].  *  * In other words, how much you need to scroll down, to make [descendant]'s bottom  * visible.  */ fun ViewGroup.howFarDownIs(descendant: View): Int? {     val bottom = Rect().also {         // See https://stackoverflow.com/a/36740277/1916449         descendant.getDrawingRect(it)         offsetDescendantRectToMyCoords(descendant, it)     }.bottom     return (bottom - height - scrollY).takeIf { it > 0 } } 

I also fixed TextInputLayout.setError() leaves empty space after clearing the error in the same class.

like image 90
arekolek Avatar answered Sep 28 '22 19:09

arekolek