TextInputLayout包含一个EditText,后者又接收来自用户的输入。使用Android设计支持库中引入的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>
Java代码将错误设置为TextInputLayout:
uidTextInputLayout.setError("Incorrect cardnumber");
如何在用户没有看到错误消息的情况下确保错误消息可见?是否可以移动焦点?
答案 0 :(得分:7)
为了确保错误消息在用户没有看到它的情况下可见,我将TextInputLayout
子类化并放在ScrollView
内。这使我可以在需要时向下滚动以显示错误消息,在每次设置错误消息时。使用它的activity / fragment类中不需要进行任何更改。
/**
* [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 { findParentOfType<ScrollView>() }
private val nestedScrollView by lazy { findParentOfType<NestedScrollView>() }
private fun scrollIfNeeded() {
// Wait a bit (like 10 frames) for other UI changes to happen
postDelayed({
scrollView?.scrollDownTo(this)
nestedScrollView?.scrollDownTo(this)
}, 160)
}
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() leaves empty space after clearing the error。
答案 1 :(得分:4)
这实际上是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
}
}```
答案 2 :(得分:2)
您应该将所有内容放在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>
答案 3 :(得分:2)
@ 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
}
}
答案 4 :(得分:0)
这很hacky,但这就是我为解决这个问题所做的工作:
因为在这种情况下我的TextInputLayout / EditText组合存在于RecyclerView中,所以我只需在设置错误时将其向上滚动:
textInputLayout.setError(context.getString(R.string.error_message))
recyclerView.scrollBy(0, context.convertDpToPixel(24f))
它有效,但肯定不太理想。如果谷歌能解决这个问题会很好,因为它肯定是一个错误。
答案 5 :(得分:0)
晚会晚了,但是如果您已经在ScrollView / RecyclerView中,则可以通过以下简便方法解决此问题:
editText.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
scrollBy(0,context.resources.getDimensionPixelSize(R.dimen.your_desired_dimension))
// i would recommend 24dp
}
}
答案 6 :(得分:-1)
我发现如果你把容器放在固定的高度,键盘会留出错误文字的空间
<FrameLayout
android:layout_width="match_parent"
android:layout_height="75dp"
android:layout_alignParentBottom="true">
<android.support.design.widget.TextInputLayout
android:id="@+id/text_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:errorEnabled="true"
app:errorTextAppearance="@style/ErrorText">
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionGo"
android:inputType="textPersonName"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
</FrameLayout>