Center hint and EditText vertically in TextInputLayout Center hint and EditText vertically in TextInputLayout android android

Center hint and EditText vertically in TextInputLayout


This doesn't seem to be possible with the current implementation of TextInputLayout. But you can achieve what you want by playing with the padding of the TextInputEditText.

Let's say you have a TextInputLayout and a TextInputEditText like this:

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

enter image description here

enter image description here

As you can see the TextInputLayout is composed of a top area to hold the hint in small version and a bottom area to hold the hint in big version (and also the input content). When the view loses focus and the edit text is empty, the hint is moving inside the blue space. On the other hand when the view gains focus or the edit text has some text inside, the hint is moving to the red space.

So what we want to do is:

  • add an extra padding to the bottom of the TextInputEditText when it doesn't have focus and text inside, this padding is equal to the red area height;
  • remove this padding when the TextInputEditText has focus or text inside.

As a result the view will look like this with the big hint vertically centered:enter image description here

Let's say you retrieve your views as follow:

private lateinit var textInputLayout: TextInputLayoutprivate lateinit var textInputEditText: TextInputEditTextoverride 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)    ...}

Here is an example of implementation that you can use to compute the top red space in pixels.

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}

Then you can update the padding like this:

private fun updateHintPosition(hasFocus: Boolean, hasText: Boolean) {    if (hasFocus || hasText) {        textInputEditText.setPadding(0, 0, 0, 0)    } else {        textInputEditText.setPadding(0, 0, 0, getTextInputLayoutTopSpace())    }}

Now you have to call this method in two places: when the view is created (in fact we need to wait for the view to be fully measured) and when the focus changes.

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

One problem is that the height of the TextInputLayout is changing so all the view is moving and it doesn't really look centered. You can fix this by putting the TextInputLayout inside a FrameLayout with a fixed height and center it vertically.

Finally you can animate all the thing. You simply need to use the TransitionManager of the support library when changing the padding.

You can see the final result in this link: https://streamable.com/la9uk

The complete code will look like this:

The layout:

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

The code:

private lateinit var textInputLayout: TextInputLayoutprivate lateinit var textInputEditText: TextInputEditTextoverride 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}

I hope this will solve your problem.


I faced with this issue, when i used theme "Widget.MaterialComponents.TextInputLayout.FilledBox.Dense" and password visibility toggle button.

So I ended up creating custom class, based on answers from this question.

Before:BeforeAfter:After

Custom class:

package com.mycompanyimport android.content.Contextimport android.util.AttributeSetimport android.view.Viewimport android.view.ViewTreeObserverimport com.google.android.material.textfield.TextInputEditTextimport com.google.android.material.textfield.TextInputLayoutimport com.mycompany.Rclass 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}

Usage:

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


The issue was resolved on 5th April 2019. Here is the commit:https://github.com/material-components/material-components-android/commit/4476564820ff7a12f94ffa7fc8d9e10221b18eb1

You can use the new and latest version (on 23rd July 2020) where the bug was resolved. Have a look the changelog ("TextInputLayout" section):https://github.com/material-components/material-components-android/releases/tag/1.3.0-alpha02

Just update the library in your gradle:

implementation 'com.google.android.material:material:1.3.0-alpha02'

It worked for me.