Подсказка по центру и 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
}

Я надеюсь, что это решит вашу проблему.

Просто добавьте paddingVertical в editText или его потомке

Я столкнулся с этой проблемой, когда использовал тему "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>

он отлично работает для меня.

Другие вопросы по тегам