I am using a TextInputLayout to show a hint but I am not able to center it vertically. I always get this:
And I would like to center the hint vertically when there is no text in the EditText / TextInputEditText. I have tried the basic ideas (gravity, layout_gravity, etc.). So far the only way to do it would be to add some "magic" padding, but I would like to do it in a cleaner way. I was thinking on measure the top hint label height and add it as a bottom margin when it is not visible, and remove the same margin when it is visible, but I don't understand very well the TextInputLayout source code yet. Does anybody know how to do it?
Edit:
I tried this suggested answer:
<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>
And I get this:
The "big" hint is still not vertically centered. It is a bit below the center because the "small" hint (in grey background, at the top, visible only when the field is focused) takes some space at the top and pushes the EditText.
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>
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:
TextInputEditText
when it doesn't have focus and text inside, this padding is equal to the red area height;TextInputEditText
has focus or text inside.As a result the view will look like this with the big hint vertically centered:
Let's say you retrieve your views as follow:
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)
...
}
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: 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
}
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: After:
Custom class:
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
}
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>
Just add paddingVertical in the editText or its descendant
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.
Please use this code If you want to show EditText hint in Center
<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" />
If you want to show EditText hint in Vertically center as well as left Aligned
<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" />
Or
<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" />
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With