web-dev-qa-db-ja.com

TextInputLayoutのエラーテキストがキーボードで覆われている

TextInputLayoutにはEditTextが含まれており、EditTextはユーザーからの入力を受け取ります。 Android Design Support Libraryで導入されたTextInputLayoutでは、エラーをEditText自体ではなくEditTextを保持するTextInputLayoutに設定することになっています。UIを書き込むときは、EditTextとエラーをカバーするキーボードにつながる可能性のあるTextInputLayout全体ではありません。次のGIFでは、ユーザーが最初にキーボードを削除してエラーメッセージを表示する必要があることに注意してください。これをIMEアクションの設定と組み合わせて、キーボードの使用を続行すると、混乱する結果。

example error

レイアウト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");

ユーザーがエラーメッセージを表示するように操作しなくても、エラーメッセージが確実に表示されるようにするにはどうすればよいですか?フォーカスを移動することは可能ですか?

35
Franzaine

エラーメッセージが表示されるようにユーザーが操作しなくても確実に表示されるように、TextInputLayoutをサブクラス化し、ScrollView内に配置しました。これにより、必要に応じて、下にスクロールしてエラーメッセージを表示できます。エラーメッセージが設定されるたびに、それを使用するアクティビティ/フラグメントクラスに変更は必要ありません。

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()
    }
}

ヘルパーメソッドは次のとおりです。

/**
 * 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()がエラーをクリアした後に空のスペースを残す も修正しました。

8
arekolek

これは実際には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
    }
}```
4
elFonzo

すべてを 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>
2
netpork

@ 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
}



}
2
ajohnston777

それはハッキーですが、これを回避するために私がやったことは次のとおりです:

この場合、私のTextInputLayout/EditTextコンボはRecyclerView内に存在するため、エラーを設定するときに単にスクロールアップします。

textInputLayout.setError(context.getString(R.string.error_message))
recyclerView.scrollBy(0, context.convertDpToPixel(24f))

動作しますが、理想的とは言えません。これは間違いなくバグであるため、Googleがこれを修正するのは素晴らしいことです。

0
Chantell Osejo