TextInputLayoutにはEditTextが含まれており、EditTextはユーザーからの入力を受け取ります。 Android Design Support Libraryで導入されたTextInputLayoutでは、エラーをEditText自体ではなくEditTextを保持するTextInputLayoutに設定することになっています。UIを書き込むときは、EditTextとエラーをカバーするキーボードにつながる可能性のあるTextInputLayout全体ではありません。次のGIFでは、ユーザーが最初にキーボードを削除してエラーメッセージを表示する必要があることに注意してください。これをIMEアクションの設定と組み合わせて、キーボードの使用を続行すると、混乱する結果。
レイアウトxmlコード:
<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>
エラーをTextInputLayoutに設定するJavaコード:
uidTextInputLayout.setError("Incorrect cardnumber");
ユーザーがエラーメッセージを表示するように操作しなくても、エラーメッセージが確実に表示されるようにするにはどうすればよいですか?フォーカスを移動することは可能ですか?
エラーメッセージが表示されるようにユーザーが操作しなくても確実に表示されるように、TextInputLayout
をサブクラス化し、ScrollView
内に配置しました。これにより、必要に応じて、下にスクロールしてエラーメッセージを表示できます。エラーメッセージが設定されるたびに、それを使用するアクティビティ/フラグメントクラスに変更は必要ありません。
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()
}
}
ヘルパーメソッドは次のとおりです。
/**
* 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 }
}
同じクラスで TextInputLayout.setError()がエラーをクリアした後に空のスペースを残す も修正しました。
これは実際にはGoogleの既知の問題です。
https://issuetracker.google.com/issues/37051832
彼らが提案する解決策は、カスタムのTextInputEditTextクラスを作成することです。
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
}
}```
すべてを ScrollView コンテナに入れて、ユーザーが少なくともスクロールしてエラーメッセージを表示できるようにする必要があります。それは私のために働いた唯一のものです。
<ScrollView
Android:layout_width="fill_parent"
Android:layout_height="fill_parent" >
<LinearLayout
Android:layout_width="fill_parent"
Android:layout_height="fill_parent"
Android:orientation="vertical" >
...
other views
...
</LinearLayout>
</ScrollView>
@ user2221404の回答が機能しなかったため、getMyParent()メソッドを次のように変更しました。
class CustomTextInputEditText @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = Android.R.attr.editTextStyle
) : TextInputEditText(context, attrs, defStyleAttr) {
private val parentRect = Rect()
override fun getFocusedRect(rect: Rect?) {
super.getFocusedRect(rect)
rect?.let {
getTextInputLayout()?.getFocusedRect(parentRect)
rect.bottom = parentRect.bottom
}
}
override fun getGlobalVisibleRect(rect: Rect?, globalOffset: Point?): Boolean {
val result = super.getGlobalVisibleRect(rect, globalOffset)
rect?.let {
getTextInputLayout()?.getGlobalVisibleRect(parentRect, globalOffset)
rect.bottom = parentRect.bottom
}
return result
}
override fun requestRectangleOnScreen(rect: Rect?): Boolean {
val result = super.requestRectangleOnScreen(rect)
val parent = getTextInputLayout()
// 10 is a random magic number to define a rectangle height.
parentRect.set(0, parent?.height ?: 10 - 24, parent?.right ?: 0, parent?.height?: 0)
parent?.requestRectangleOnScreen(parentRect, true /*immediate*/)
return result
}
private fun getTextInputLayout(): TextInputLayout? {
var parent = parent
while (parent is View) {
if (parent is TextInputLayout) {
return parent
}
parent = parent.getParent()
}
return null
}
}
それはハッキーですが、これを回避するために私がやったことは次のとおりです:
この場合、私のTextInputLayout/EditTextコンボはRecyclerView内に存在するため、エラーを設定するときに単にスクロールアップします。
textInputLayout.setError(context.getString(R.string.error_message))
recyclerView.scrollBy(0, context.convertDpToPixel(24f))
動作しますが、理想的とは言えません。これは間違いなくバグであるため、Googleがこれを修正するのは素晴らしいことです。