Подсказка по центру и EditText по вертикали в TextInputLayout
Я использую TextInputLayout, чтобы показать подсказку, но я не могу центрировать его по вертикали. Я всегда получаю это:
И я хотел бы центрировать подсказку по вертикали, когда в EditText / TextInputEditText нет текста. Я попробовал основные идеи (гравитация, layout_gravity и т. Д.). Пока что единственный способ сделать это - добавить немного "волшебного" заполнения, но я бы хотел сделать это более чистым способом. Я думал о том, как измерить высоту метки верхней подсказки и добавить ее в качестве нижнего поля, когда она не видима, и удалить то же поле, когда она видима, но я пока не очень хорошо понимаю исходный код TextInputLayout. Кто-нибудь знает, как это сделать?
Редактировать:
Я попробовал этот предложенный ответ:
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:background="@color/grey_strong">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="90dp"
android:background="@color/red_light"
android:gravity="center_vertical"
android:hint="Test"/>
</android.support.design.widget.TextInputLayout>
И я получаю это:
"Большой" намек все еще не центрирован по вертикали. Это немного ниже центра, потому что "маленький" совет (на сером фоне, вверху, видимый только тогда, когда поле сфокусировано) занимает некоторое место сверху и толкает EditText.
12 ответов
Это кажется невозможным с текущей реализацией TextInputLayout
, Но вы можете достичь того, что вы хотите, играя с дополнением TextInputEditText
,
Допустим, у вас есть TextInputLayout
и TextInputEditText
как это:
<android.support.design.widget.TextInputLayout
android:id="@+id/text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FAA"
android:hint="Text hint">
<android.support.design.widget.TextInputEditText
android:id="@+id/text_input_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#AAF" />
</android.support.design.widget.TextInputLayout>
Как вы можете видеть TextInputLayout
состоит из верхней области для хранения подсказки в небольшой версии и нижней области для хранения подсказки в большой версии (а также входного содержимого). Когда представление теряет фокус, а текст редактирования пуст, подсказка перемещается внутри синего пространства. С другой стороны, когда представление получает фокус или текст редактирования имеет некоторый текст внутри, подсказка перемещается в красное пространство.
Итак, что мы хотим сделать, это:
- добавить дополнительные отступы в нижней части
TextInputEditText
когда у него нет фокуса и текста внутри, этот отступ равен высоте красной области; - удалите этот отступ, когда
TextInputEditText
имеет фокус или текст внутри.
В результате вид будет выглядеть так: большая подсказка расположена по центру:
Допустим, вы получаете свои взгляды следующим образом:
private lateinit var textInputLayout: TextInputLayout
private lateinit var textInputEditText: TextInputEditText
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
...
textInputLayout = view.findViewById(R.id.text_input_layout)
textInputEditText = view.findViewById(R.id.text_input_edit_text)
...
}
Вот пример реализации, которую вы можете использовать для вычисления верхнего красного пространства в пикселях.
private fun getTextInputLayoutTopSpace(): Int {
var currentView: View = textInputEditText
var space = 0
do {
space += currentView.top
currentView = currentView.parent as View
} while (currentView.id != textInputLayout.id)
return space
}
Затем вы можете обновить отступы так:
private fun updateHintPosition(hasFocus: Boolean, hasText: Boolean) {
if (hasFocus || hasText) {
textInputEditText.setPadding(0, 0, 0, 0)
} else {
textInputEditText.setPadding(0, 0, 0, getTextInputLayoutTopSpace())
}
}
Теперь вы должны вызывать этот метод в двух местах: когда создается представление (на самом деле нам нужно дождаться его полного измерения) и когда фокус изменяется.
textInputLayout.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
if (textInputLayout.height > 0) {
textInputLayout.viewTreeObserver.removeOnPreDrawListener(this)
updateHintPosition(textInputEditText.hasFocus(), !textInputEditText.text.isNullOrEmpty())
return false
}
return true
}
})
textInputEditText.setOnFocusChangeListener { _, hasFocus ->
updateHintPosition(hasFocus, !textInputEditText.text.isNullOrEmpty())
}
Одна проблема заключается в том, что высота TextInputLayout
меняется, поэтому весь вид движется, и он не выглядит по центру. Вы можете исправить это, поставив TextInputLayout
внутри FrameLayout
с фиксированной высотой и отцентрируйте его вертикально.
Наконец, вы можете оживить все это. Вам просто нужно использовать TransitionManager
библиотеки поддержки при изменении отступов.
Вы можете увидеть окончательный результат по этой ссылке: https://streamable.com/la9uk
Полный код будет выглядеть так:
Расположение:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="60dp"> <-- Adapt the height for your needs -->
<android.support.design.widget.TextInputLayout
android:id="@+id/text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="#FAA"
android:hint="Text hint">
<android.support.design.widget.TextInputEditText
android:id="@+id/text_input_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#AAF" />
</android.support.design.widget.TextInputLayout>
</FrameLayout>
Код:
private lateinit var textInputLayout: TextInputLayout
private lateinit var textInputEditText: TextInputEditText
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
val view = inflater.inflate(R.layout.your_layout, container, false)
textInputLayout = view.findViewById(R.id.text_input_layout)
textInputEditText = view.findViewById(R.id.text_input_edit_text)
textInputLayout.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
// Wait for the first draw to be sure the view is completely measured
if (textInputLayout.height > 0) {
textInputLayout.viewTreeObserver.removeOnPreDrawListener(this)
updateHintPosition(textInputEditText.hasFocus(), !textInputEditText.text.isNullOrEmpty(), false)
return false
}
return true
}
})
textInputEditText.setOnFocusChangeListener { _, hasFocus ->
updateHintPosition(hasFocus, !textInputEditText.text.isNullOrEmpty(), true)
}
return view
}
private fun updateHintPosition(hasFocus: Boolean, hasText: Boolean, animate: Boolean) {
if (animate) {
TransitionManager.beginDelayedTransition(textInputLayout)
}
if (hasFocus || hasText) {
textInputEditText.setPadding(0, 0, 0, 0)
} else {
textInputEditText.setPadding(0, 0, 0, getTextInputLayoutTopSpace())
}
}
private fun getTextInputLayoutTopSpace(): Int {
var currentView: View = textInputEditText
var space = 0
do {
space += currentView.top
currentView = currentView.parent as View
} while (currentView.id != textInputLayout.id)
return space
}
Я надеюсь, что это решит вашу проблему.
Я столкнулся с этой проблемой, когда использовал тему "Widget.MaterialComponents.TextInputLayout.FilledBox.Dense"
и кнопка переключения видимости пароля.
В итоге я создал собственный класс на основе ответов на этот вопрос.
До: h ttps:https://stackru.com/images/5de6156a37414471cde6899fac31ceb763ceffc9.png После: h ttps:https://stackru.com/images/5545238e733d8127c5e52de61024b919023895ab.png
Пользовательский класс:
package com.mycompany
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewTreeObserver
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.mycompany.R
class CustomTextInputEditText : TextInputEditText {
//region Constructors
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
//endregion
//region LifeCycle
override fun onAttachedToWindow() {
super.onAttachedToWindow()
textInputEditText.setOnFocusChangeListener { _, hasFocus ->
updateHintPosition(hasFocus, !textInputEditText.text.isNullOrEmpty())
}
textInputEditText.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
if ((textInputLayout?.height ?: 0) > 0) {
textInputLayout?.viewTreeObserver?.removeOnPreDrawListener(this)
updateHintPosition(textInputEditText.hasFocus(), !textInputEditText.text.isNullOrEmpty())
return false
}
return true
}
})
}
//endregion
//region Center hint
private var paddingBottomBackup:Int? = null
private var passwordToggleButtonPaddingBottomBackup:Float? = null
private val textInputEditText: TextInputEditText
get() {
return this
}
private val textInputLayout:TextInputLayout?
get(){
return if (parent is TextInputLayout) (parent as? TextInputLayout) else (parent?.parent as? TextInputLayout)
}
private val passwordToggleButton:View?
get() {
return (parent as? View)?.findViewById(R.id.text_input_password_toggle)
}
private fun updateHintPosition(hasFocus: Boolean, hasText: Boolean) {
if (paddingBottomBackup == null)
paddingBottomBackup = paddingBottom
if (hasFocus || hasText)
textInputEditText.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottomBackup!!)
else
textInputEditText.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottomBackup!! + getTextInputLayoutTopSpace())
val button = passwordToggleButton
if (button != null){
if (passwordToggleButtonPaddingBottomBackup == null)
passwordToggleButtonPaddingBottomBackup = button.translationY
if (hasFocus || hasText)
button.translationY = - getTextInputLayoutTopSpace().toFloat() * 0.50f
else
button.translationY = passwordToggleButtonPaddingBottomBackup!!
}
}
private fun getTextInputLayoutTopSpace(): Int {
var currentView: View = textInputEditText
var space = 0
do {
space += currentView.top
currentView = currentView.parent as View
} while (currentView !is TextInputLayout)
return space
}
//endregion
//region Internal classes
data class Padding(val l: Int, val t: Int, val r: Int, val b: Int)
//endregion
}
Использование:
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.Dense"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:hint="Password"
app:passwordToggleEnabled="true">
<com.mycompany.CustomTextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
Проблема была решена 5 апреля 2019 года. Вот фиксация:https://github.com/material-components/material-components-android/commit/4476564820ff7a12f94ffa7fc8d9e10221b18eb1
Вы можете использовать новую и последнюю версию (от 23 июля 2020 г.), в которой ошибка была устранена. Взгляните на журнал изменений (раздел "TextInputLayout"):https://github.com/material-components/material-components-android/releases/tag/1.3.0-alpha02.
Просто обновите библиотеку в своем Gradle:
implementation 'com.google.android.material:material:1.3.0-alpha02'
У меня это сработало.
Возможно, немного поздно, но... на основе ответа @JFrite... Вы можете создать класс для улучшения кода:
class TextInputCenterHelper constructor(val textInputLayout: TextInputLayout, val textInputEditText: TextInputEditText){
init {
textInputLayout.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
if (textInputLayout.height > 0) {
textInputLayout.viewTreeObserver.removeOnPreDrawListener(this)
updateHintPosition(textInputEditText.hasFocus(), !textInputEditText.text.isNullOrEmpty(), false)
return false
}
return true
}
})
textInputEditText.setOnFocusChangeListener { _, hasFocus ->
updateHintPosition(hasFocus, !textInputEditText.text.isNullOrEmpty(), true)
}
}
private fun updateHintPosition(hasFocus: Boolean, hasText: Boolean, animate: Boolean) {
if (animate) {
TransitionManager.beginDelayedTransition(textInputLayout)
}
if (hasFocus || hasText) {
textInputEditText.setPadding(0, 0, 0, 0)
} else {
textInputEditText.setPadding(0, 0, 0, getTextInputLayoutTopSpace())
}
}
private fun getTextInputLayoutTopSpace(): Int {
var currentView: View = textInputEditText
var space = 0
do {
space += currentView.top
currentView = currentView.parent as View
} while (currentView.id != textInputLayout.id)
return space
}
И для его использования:
TextInputCenterHelper(your_text_input_layout, your_text_input_edit_text)
Надеюсь, это кому-то поможет!
Пожалуйста, используйте этот код, если вы хотите показать подсказку EditText в центре
<android.support.design.widget.TextInputLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="90dp"
android:hint="Test"
android:gravity="center" />
Если вы хотите показать подсказку EditText по центру по вертикали, а также по левому краю
<android.support.design.widget.TextInputLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="90dp"
android:hint="Test"
android:gravity="center_vertical" />
Или же
<android.support.design.widget.TextInputLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="90dp"
android:hint="Test"
android:gravity="center|left" />
Попробуй это
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/inputLoginEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:hintEnabled="false"> <-- This is important line
Нашел решение:
<android.support.design.widget.TextInputLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<android.support.design.widget.TextInputEditText
android:layout_width="200dp"
android:layout_height="90dp"
android:hint="Test"
android:gravity="center_vertical" />
90dp как высота была просто в качестве примера. Если бы вы могли предоставить свой XML, я мог бы адаптировать его к вашему делу. Вы просто должны установить android:gravity="center_vertical"
и TextInputLayout
должен иметь wrap_content
как высота
Надеюсь, поможет:)
Я обнаружил несколько проблем с решениями, размещенными здесь, поэтому добавил еще одно, которое немного лучше охватывает основы:
public class CenteredTextInputEditText extends TextInputEditText {
private Integer paddingBottomBackup = null;
// region Constructors
public CenteredTextInputEditText(Context context) {
super(context);
}
public CenteredTextInputEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CenteredTextInputEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
// endregion
// region LifeCycle
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (getOnFocusChangeListener() == null) {
setOnFocusChangeListener((v, hasFocus) -> updateHintPosition(hasFocus));
}
getViewTreeObserver()
.addOnPreDrawListener(
new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (getHeight() > 0) {
getViewTreeObserver().removeOnPreDrawListener(this);
updateHintPosition(hasFocus());
return false;
}
return true;
}
});
}
// endregion
// region Center hint
private void updateHintPosition(boolean hasFocus) {
boolean hasText = getText() != null && !Strings.isNullOrEmpty(getText().toString());
if (paddingBottomBackup == null) {
paddingBottomBackup = getPaddingBottom();
}
int bottomPadding = paddingBottomBackup;
if (!hasFocus && !hasText) {
bottomPadding += getTextInputTopSpace();
}
setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), bottomPadding);
if (hasFocus) {
KeyboardUtils.openKeyboardFrom(this);
}
}
private int getTextInputTopSpace() {
View currentView = this;
int space = 0;
do {
space += currentView.getTop();
currentView = (View) currentView.getParent();
} while (!(currentView instanceof TextInputLayout));
return space;
}
// endregion
public void addAdditionalFocusListener(Consumer<Boolean> consumer) {
setOnFocusChangeListener(
new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
consumer.accept(hasFocus);
updateHintPosition(hasFocus);
}
});
}
}
Самый простой способ, который я нашел для центрирования TextInput Layout, - это просто включить SetHelperEnabled в моем XML или программно вставить эти свойства в TextInputLayout.
app:helperTextEnabled="true"
app:helperText=" "
Вы также можете дополнительно настроить дополнительные отступы, добавив
paddingTop
во внутренний EditText
<androidx.cardview.widget.CardView
android:id="@+id/cardView2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/textView2"
app:layout_constraintTop_toBottomOf="@+id/textViewSeekModText">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="409dp"
android:layout_height="wrap_content"
android:background="#FDFDFD"
app:helperText=" "
app:helperTextEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:hint="hint" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.cardview.widget.CardView>
Вот как мы можем достичь
Чтобы центрировать подсказку, используйте
app:boxBackgroundMode="filled"
Если вы хотите удалить нижнюю строку текста редактирования, используйте
app:boxStrokeWidth="0dp"
app:boxStrokeWidthFocused="0dp"
app:boxStrokeColor="@color/white"
app:boxBackgroundColor="@color/white"
Полный код
<com.google.android.material.textfield.TextInputLayout
app:boxBackgroundMode="filled"
app:boxStrokeWidth="0dp"
app:boxStrokeWidthFocused="0dp"
app:boxStrokeColor="@color/white"
app:boxBackgroundColor="@color/white"
android:id="@+id/layout_main"
android:paddingVertical="@dimen/dp_6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dp_4"
android:hint="@string/your_hint"
android:paddingHorizontal="@dimen/dp_16"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/ed_txt"
android:inputType="textNoSuggestions"
android:layout_width="match_parent"
android:layout_height="?listPreferredItemHeightSmall"
android:gravity="bottom"
android:paddingVertical="@dimen/dp_8" />
</com.google.android.material.textfield.TextInputLayout>
Я знаю, что это не лучшее решение, но оно работает. Вам просто нужно добавить отступ к «TextInputEditText»:
android:paddingTop="18dp"
android:paddingBottom="18dp"
Полный пример:
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/inputLoginEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"
android:layout_marginTop="5dp"
android:backgroundTint="@color/light_blue_background"
android:textColor="@color/blue_button"
app:hintEnabled="false"
android:layout_gravity="center"
app:boxStrokeWidth="0dp"
app:boxStrokeWidthFocused="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textLabelEmail"
app:passwordToggleEnabled="false">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etLoginEmail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:backgroundTint="@color/light_blue_background"
android:hint="@string/hint_email"
android:inputType="textEmailAddress"
android:paddingTop="18dp"
android:paddingBottom="18dp"
android:maxLines="1"
android:textColor="@color/blue_button" />
</com.google.android.material.textfield.TextInputLayout>
он отлично работает для меня.