One component of the Design Support Library that we didn’t cover in the earlier series on that library was the new TextInputLayout which adds some nice additions to a standard EditText control to provide improved hint and error text handling. In this article we’ll look at how to use it, and avoid the one gotcha with error text.
Although TextView (which EditText subclasses) already has some support for hint text and error text handling, they are a little dated, don’t really embrace material design, and can, in some cases, can actually be quite difficult to use. TextInputLayout improves upon this by adding a wrapper around a standard EditText which will give us a much more material implementation of these.
So we need to start by importing the design support library in to our project:
apply plugin: 'com.android.application' android { compileSdkVersion 23 buildToolsVersion "23.0.0" defaultConfig { applicationId "com.stylingandroid.textinputlayout" minSdkVersion 7 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile 'com.android.support:design:23.0.0' }
Next we need to create a layout in which we wrap our EditText inside a TextInputLayout:
The additional View in here is just to hold the initial focus when the layout is first loaded. The reason for this is that the position, colour, and size of the hint text will change as the EditText receives focus, so having this focus holder View enables us to see this transition.
The only thing of note with the TextInputLayout declaration is that we specify
app:errorEnabled="true"
to enable the error text display support – more on this in a moment.The next thing that we need to do is hook all of this up in our Activity:
public class MainActivity extends Activity { private static final int MIN_TEXT_LENGTH = 4; private static final String EMPTY_STRING = ""; private TextInputLayout textInputLayout; private EditText editText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textInputLayout = (TextInputLayout) findViewById(R.id.text_input_layout); editText = (EditText) findViewById(R.id.edit_text); textInputLayout.setHint(getString(R.string.hint)); editText.setOnEditorActionListener(ActionListener.newInstance(this)); } private boolean shouldShowError() { int textLength = editText.getText().length(); return textLength > 0 && textLength < MIN_TEXT_LENGTH; } private void showError() { textInputLayout.setError(getString(R.string.error)); } private void hideError() { textInputLayout.setError(EMPTY_STRING); } private static final class ActionListener implements TextView.OnEditorActionListener { private final WeakReference<MainActivity> mainActivityWeakReference; public static ActionListener newInstance(MainActivity mainActivity) { WeakReference<MainActivity> mainActivityWeakReference = new WeakReference<>(mainActivity); return new ActionListener(mainActivityWeakReference); } private ActionListener(WeakReference<MainActivity> mainActivityWeakReference) { this.mainActivityWeakReference = mainActivityWeakReference; } @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { MainActivity mainActivity = mainActivityWeakReference.get(); if (mainActivity != null) { if (actionId == EditorInfo.IME_ACTION_GO && mainActivity.shouldShowError()) { mainActivity.showError(); } else { mainActivity.hideError(); } } return true; } } }So this is actually pretty straightforward. We set some hint text on the TextInputLayout and that’s our hints done! The error text requires a little more effort but that’s only because we need to add some logic to perform the text validation (in this case we require at least 4 characters to be entered) and then show and hide the error text as appropriate.
There is a potential gotcha here. Initially I tried setting the error string and toggling the visibility by calling
setErrorEnabled(true|false)
but found that the error was not displaying. The source for this control is not yet available in AOSP so I used the decompiler built in to Android Studio to examine the code to understand what was going wrong. I found thatsetErrorEnabled()
actually creates and destroys a TextView object, whereas I was expecting it to simply toggle the visibility. So the reason that my error was not displaying was because it requiredsetError(String error)
to be called aftersetErrorEnabled(true)
.After gaining this understanding I decided to change my implementation to reduce the object creation / destruction overhead implicit in using setErrorEnabled() to toggle error visibility and use an empty string when no error should be shown.
The only other thing worth explaining in here is that I always like to make my inner classes static to guard against Context leaks. Any non-static inner class holds an implicit reference to its parent class and, if the parent is a Context, an instance of the inner class which lives longer than it’s parent class will hold a reference to the parent and prevent it from being GC’d – essentially leaking a Context which is bad! By making the inner class static and holding a WeakReference to the parent class we ensure that the inner class does not prevent the parent from being GC’d.
Let’s now take a look at this in action:
This is fine if we’re happy with the default styles, but what if we need to apply a different look? The EditText itself can be styled as a normal EditText, but we can apply a custom TextAppearance style to both the hint and error text. Here’s an example of overriding the error text style:
<android.support.design.widget.TextInputLayout android:id="@+id/text_input_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" app:errorEnabled="true" app:errorTextAppearance="@style/ErrorText"> <EditText android:id="@+id/edit_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:imeOptions="actionGo" android:inputType="text" android:singleLine="true" /> </android.support.design.widget.TextInputLayout><?xml version="1.0" encoding="utf-8"?> <resources> <style name="AppTheme" parent="BaseAppTheme" /> <style name="BaseAppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="colorPrimary">@color/sa_green</item> <item name="colorAccent">@color/sa_accent</item> </style> <style name="ErrorText" parent="TextAppearance.AppCompat.Small"> <item name="android:textStyle">bold|italic</item> <item name="android:textColor">@color/error_text_colour</item> </style> </resources>We can now see this change:
That’s it – provided that you are aware of the underlying behaviour which will cause the error text to be reset when
setErrorEnabled(false)
is called then TextInputLayout is really quite easy to use and gives an easy win for adding some material style polish to your EditText.The source code for this article is available here.
© 2015, Mark Allison. All rights reserved.
Copyright © 2015 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.
Hi Mark, where did you set your hint text in this example? And would you be so kind to add the sample repo – tried to look this issue up in the code but 404 not found 😉 Thanks!
Hello Mark, do you solve a problem when keyboard overlaps error, when EditText is placed too low on the screen?
How to have the TextInputLayout hint animation only when a character is typed not when it gains focus?
The code does not work it does not trigger the Listener. Emulator Nexus 5 API23