Center hint and EditText vertically in TextInputLayout
Asked Answered
F

12

21

I am using a TextInputLayout to show a hint but I am not able to center it vertically. I always get this:

enter image description here

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:

enter image description here

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.

Fauman answered 9/1, 2018 at 21:0 Comment(2)
Probably the issue is because your heights are wrap_content you need to specify height of TextInputLayout and then use match_parent for EditText height and set the gravity to centre_verticalDurning
@Durning I tried that, but it is still not centered. Is there a more dynamic way to do it while keeping height as wrap_content?Fauman
C
13

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

Corroborant answered 11/6, 2018 at 12:49 Comment(2)
This is good but appears to add too much padding at the bottom. Perhaps we're checking the wrong attribute?. edit: Actually, when I just divide the getTextInputLayoutTopSpace() result by 2 it looks perfect.Diapason
@Corroborant I didn't end up using the same code, but I got some ideas from your code. I have marked this answer as the right one (sorry for taking so long to do that).Fauman
N
8

Just add paddingVertical in the editText or its descendant

Naval answered 19/2, 2021 at 15:30 Comment(1)
The most easiest way!Phaidra
C
7

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: Before After: 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>
Copeland answered 1/4, 2019 at 11:50 Comment(1)
text_input_password_toggle not found errorArnitaarno
T
3

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.

Trapezius answered 3/8, 2020 at 13:11 Comment(0)
A
1

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

Analgesic answered 10/1, 2018 at 6:20 Comment(1)
Hi. This didn't work for me. In the end I used @alenz316 suggestion combined with a layout listener.Fauman
H
1

Maybe a bit late but... based on @JFrite reply... You can create a class to improve the code:

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
}

And for use it:

 TextInputCenterHelper(your_text_input_layout, your_text_input_edit_text)

I hope this can help someone!

Hylophagous answered 24/7, 2019 at 8:40 Comment(2)
Good idea, but not working well with passwordToggleButton, as well needs to add padding left/padding right instead of zerosGloam
@Gloam yes, for solve that problem I created an custom view, it's not very clear, but can be a "quick fix"Awed
S
0

Found a solution:

    <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 as height was just as an example. If you could provide your xml I could adapt it to your case. You just have to set android:gravity="center_vertical" and the TextInputLayout should have wrap_content as height

Hope it helps :)

Spicebush answered 9/1, 2018 at 21:12 Comment(4)
Hi @Rene Ferrari. I tried your solution but it is still not centered. The floating hint takes some space in the top and the text is below the center.Fauman
would it be possible for you to share your layout xml? Just edit your question ;) This way it would be easier to find a solution that suits your caseSpicebush
@Fauman is right, TextInputLayout puts top margin on the nested EditText offsetting the entire things. Adding android:layout_marginBottom="@dimen/abc_text_size_caption_material" to the nested EditText should the same space below essentially centering it.Insane
@Insane I used this in combination with a layout listener to show the text and/or hint vertically centered all the time, even when there is no text and the field is focused.Fauman
A
0

I found a few issues with the solutions posted here, so adding one more that covers the bases a little better:

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);
          }
        });
  }
}
Ahrendt answered 16/9, 2020 at 22:53 Comment(0)
G
0

The easiest way I found to center TextInput Layout was to just enable SetHelperEnabled in my XML or by inserting these properties programmatically in the TextInputLayout

app:helperTextEnabled="true"
app:helperText=" "

enter image description here enter image description here

You can also further adjust the extra padding by adding paddingTop to inner 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>
Geld answered 8/11, 2020 at 16:49 Comment(0)
S
0

I know it is not the best solution but it works. You just have to add padding to "TextInputEditText":

android:paddingTop="18dp"
android:paddingBottom="18dp"

Full example:

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

it woks pefectly to me.

Splinter answered 2/6, 2021 at 3:12 Comment(0)
M
0

Here's how we can achieve

To center the hint use app:boxBackgroundMode="filled"
If looking to remove the edit text bottom line use

            app:boxStrokeWidth="0dp"
            app:boxStrokeWidthFocused="0dp"
            app:boxStrokeColor="@color/white"
            app:boxBackgroundColor="@color/white"

Complete code

           <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>
Marshland answered 4/8, 2021 at 13:45 Comment(0)
A
0

Try This

<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 
Amplexicaul answered 8/11, 2023 at 5:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.