El TextInputLayout
hace parte de la lista de componentes presentados en el Material Design con el fin de estilizar los campos de texto con etiquetas flotantes.
Este presenta una animación del texto auxiliar que poseen los edit texts hacia la parte superior para crear un comportamiento práctico entre el significado del campo de texto y su contenido.
Además permite asignar mensajes de error al momento de validar el formato en su interior.
Descargar Proyecto En Android Studio De Etiquetas Flotantes
Este tutorial está basado en una app que contendrá todos los conocimientos que vayas obteniendo.
Si quieres desbloquear el link de descarga del código, sigue estas instrucciones:
[sociallocker id=»7121″]
Etiquetas Flotantes En Material Design
El nuevo esquema de diseño de Google exige que cuando el usuario establezca el foco en un campo de texto, su hint debe flotar hacia la parte superior, proporcionando espacio para el texto. Esto asegura que el usuario nunca pierda el contexto del contenido que está digitando.
Para el texto de la entrada y el hint usa tamaños de 16sp. En el caso de la etiqueta se asigna el estilo caption de 12sp. En total el área completa mide 72dp, contando el padding entre los componentes.
Gracias a la librería de soporte para diseño, es posible crear etiquetas flotantes a través del componente TextInputLayout. Este actúa como una envoltura del widget EditText
(o sus descendientes) con el fin de crear la etiqueta.
A continuación verás un ejemplo donde crearé un formulario para añadir un nuevo cliente hipotético en una app. Este consta de tres campos de texto para el nombre, el teléfono y el correo. La idea es asignar una etiqueta flotante a cada uno y mostrar los posibles comportamientos del TextInputLayout
.
1. Uso Del TextInputLayout
Paso 1: Crear Proyecto en Android Studio
Entra a Android Studio y ve a File > New > New Project… para crear un nuevo proyecto llamado «Etiquetas Flotantes». Asigna las siguientes características:
- Versión mínima de SDK:
11
Blank Activity
como actividad principal.- Activity Name:
ActividadPrincipal
- Layout Name:
actividad_principal
- Title:
Actividad Principal
- Menu Resource Name:
menu_actividad_principal
Luego abre tu layout actividad_principal.xml
y elimina el floating action button. De la misma forma en la clase Java, elimina la referencia del fab para la asignación de la escucha.
La clase quedaría así:
ActividadPrincipal.java
import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.View; public class ActividadPrincipal extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.actividad_principal); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); } }
El layout de la actividad se vería así:
actividad_principal.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context="com.herprogramacion.etiquetasflotantes.ActividadPrincipal"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.AppBarLayout> <include layout="@layout/contenido_actividad_principal" /> </android.support.design.widget.CoordinatorLayout>
Y el segmento central contenido_actividad_principal.xml
puedes dejarlo con su contenido predeterminado
Paso 2: Añadir Design Support Library
Abre tu archivo build.gradle
del módulo principal y añade las librerías para soporte de versiones y la librería de diseño.
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.1.1' compile 'com.android.support:design:23.1.1' }
Paso 3: Cambia Paleta de Colores
Los colores que se usará para la app se basan en una paleta principal con púrpura 700 para los oscuros y púrpura en tono 500 para el color principal. La paleta de acento será ocupada por el tono 400 de Rosa como se muestra en la herramienta Material Design Colors:
El archivo colors.xml
quedaría con los siguientes valores:
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#7B1FA2</color> <color name="colorPrimaryDark">#AB47BC</color> <color name="colorAccent">#EC407A</color> </resources>
Paso 4: Diseñar UI de la Actividad
El formulario de inserción tiene varias componentes a ubicar cómo muestra el siguiente screenshot de la app.
Se tienen tres edit texts recubiertos por su respectivo TextInputLayout
. Al lado izquierdo existe una imagen representativa del contenido de cada campo de texto. Y al final existe una bottom bar con dos botones para cancelar o confirmar la inserción del cliente.
Al campo de texto del nombre asigna un inputType
del tipo text
. Para el teléfono usa el tipo phone
y para el correo el tipo textEmailAdress
.
contenido_actividad_principal.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context="com.herprogramacion.etiquetasflotantes.ActividadPrincipal" tools:showIn="@layout/actividad_principal"> <LinearLayout android:id="@+id/area_nombre" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:orientation="horizontal"> <ImageView android:id="@+id/img_cliente" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:src="@drawable/ic_cliente" /> <android.support.design.widget.TextInputLayout android:id="@+id/til_nombre" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="32dp"> <EditText android:id="@+id/campo_nombre" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="@string/hint_nombre" android:inputType="text" android:singleLine="true" /> </android.support.design.widget.TextInputLayout> </LinearLayout> <LinearLayout android:id="@+id/area_telefono" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/area_nombre" android:orientation="horizontal"> <ImageView android:id="@+id/img_correo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:src="@drawable/ic_telefono" /> <android.support.design.widget.TextInputLayout android:id="@+id/til_telefono" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="32dp"> <EditText android:id="@+id/campo_telefono" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="@string/hint_telefono" android:inputType="phone" /> </android.support.design.widget.TextInputLayout> </LinearLayout> <LinearLayout android:id="@+id/area_correo" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/area_telefono" android:orientation="horizontal"> <ImageView android:id="@+id/img_telefono" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/img_correo" android:layout_column="0" android:layout_gravity="center_vertical" android:layout_row="2" android:src="@drawable/ic_correo" /> <android.support.design.widget.TextInputLayout android:id="@+id/til_correo" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="32dp"> <EditText android:id="@+id/campo_correo" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="@string/hint_correo" android:inputType="textEmailAddress" android:singleLine="true" /> </android.support.design.widget.TextInputLayout> </LinearLayout> <!-- Bottom Bar --> <LinearLayout android:id="@+id/bottom_bar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:layout_alignParentBottom="true" android:gravity="center_vertical" android:orientation="horizontal"> <Button android:id="@+id/boton_cancelar" style="@style/Widget.AppCompat.Button.Borderless.Colored" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/accion_cancelar" /> <Button android:id="@+id/boton_aceptar" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:backgroundTint="@color/colorAccent" android:text="@string/accion_aceptar" android:textColor="@android:color/white" /> </LinearLayout> </RelativeLayout>
Cómo ves, el TextInputLayout
solo necesita encerrar al edit text al cual le proveerá la etiqueta.
<android.support.design.widget.TextInputLayout android:id="@+id/til_nombre" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="32dp"> <EditText android:id="@+id/campo_nombre" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="@string/hint_nombre" android:inputType="text" android:singleLine="true" /> </android.support.design.widget.TextInputLayout>
2. Setear Errores en el TextInputLayout
El TextInputLayout
tiene la capacidad para mostrar errores asociados al contenido digitado por usuario. Estos pueden ser debido a un formato incorrecto, una cantidad mínima de caracteres no satisfecha, algún carácter indebido, etc.
El ejemplo actual mostrará los errores necesarios luego de que se validen los campos de texto, al presionar el botón GUARDAR de la bottom bar.
Paso 1: Implementar OnClickListener en el Botón
Dentro de ActividadPrincipal.java
obtén la instancia del botón con la acción de guardado para tener su referencia. Luego asigna con setOnClickListener()
una nueva escucha anónima. Sobrescribe su controlador onClick()
y deja expresado que deseas ejecutar un método futuro llamado validarCampos()
.
Button botonAceptar = (Button)findViewById(R.id.boton_aceptar); botonAceptar.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // validarDatos() } });
Paso 2: Validación de Datos
Validar los campos de tus edit texts depende directamente de las reglas de negocio de tu aplicación. Normalmente esto va definido en el diccionario de datos si es que tienes una base de datos local o remota.
Supongamos que este ejemplo existen las siguientes reglas:
- Nombre: Solo caracteres alfabéticos con un tamaño máximo de 30.
- Teléfono: Solo caracteres numéricos con un tamaño estándar de 7.
- Email: Secuencia de caracteres que cumplan la sintaxis de correos electrónicos.
Validación del nombre del cliente — Con valor text
de inputText
no es posible limitar el campo de texto, ya que este filtro permite caracteres alfanuméricos. Una de las formas de hacerlo es a través de la clase Pattern
, la cual contiene métodos para el uso de expresiones regulares.
La expresión regular para aceptar caracteres alfabéticos y espacios es:
^[a-zA-Z ]+$
Con ello, crea un nuevo método llamado validarNombre()
y haz que retorne en boolean para usarlo como determinante de paso en el flujo de la app.
private boolean validarNombre(String nombre){ Pattern patron = Pattern.compile("^[a-zA-Z ]+$"); return patron.matcher(nombre).matches() || nombre.length() > 30; }
Validación del teléfono — El tipo de entrada para este campo de texto te ayuda a limitar al usuario al ingreso de caracteres relacionados con un número de teléfono.
Por otro lado, puedes usar el patrón predefinido Patterns.PHONE
para una validación adicional de la sintaxis del número. Añade esta lógica a un nuevo método llamado validarTelefono()
.
private boolean validadTelefono(String telefono){ return Patterns.PHONE.matcher(telefono).matches(); }
Validación de correo electrónico — Al igual que el caso anterior, la clase de utilidades Patterns
contiene un patrón para el correo electrónico con el nombre de EMAIL_ADDRESS
. Así que añade un nuevo método de validación para email con el siguiente cuerpo.
private boolean esCorreoValido(String correo){ return Patterns.EMAIL_ADDRESS.matcher(correo).matches(); }
Paso 3: Mostrar Errores en el TextInputLayout
El método setError()
se usa para mostrar los errores en la parte inferior del EditText
a través del TextInputLayout
. También puedes pasar null
como parámetro para limpiar los errores.
Teniendo en cuenta estos métodos, dirígete a cada método de validación y despliega un error si el formato no es favorable, de lo contrario límpialo.
ActividadPrincipal.java
import android.os.Bundle; import android.support.design.widget.TextInputLayout; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.util.Patterns; import android.view.View; import android.widget.Button; import android.widget.Toast; import java.util.regex.Pattern; public class ActividadPrincipal extends AppCompatActivity { private TextInputLayout tilNombre; private TextInputLayout tilTelefono; private TextInputLayout tilCorreo; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.actividad_principal); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); // Referencias TILs tilNombre = (TextInputLayout) findViewById(R.id.til_nombre); tilTelefono = (TextInputLayout) findViewById(R.id.til_telefono); tilCorreo = (TextInputLayout) findViewById(R.id.til_correo); // Referencia Botón Button botonAceptar = (Button) findViewById(R.id.boton_aceptar); botonAceptar.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { validarDatos(); } }); } private boolean esNombreValido(String nombre) { Pattern patron = Pattern.compile("^[a-zA-Z ]+$"); if (!patron.matcher(nombre).matches() || nombre.length() > 30) { tilNombre.setError("Nombre inválido"); return false; } else { tilNombre.setError(null); } return true; } private boolean esTelefonoValido(String telefono) { if (!Patterns.PHONE.matcher(telefono).matches()) { tilTelefono.setError("Teléfono inválido"); return false; } else { tilTelefono.setError(null); } return true; } private boolean esCorreoValido(String correo) { if (!Patterns.EMAIL_ADDRESS.matcher(correo).matches()) { tilCorreo.setError("Correo electrónico inválido"); return false; } else { tilCorreo.setError(null); } return true; } private void validarDatos() { String nombre = tilNombre.getEditText().getText().toString(); String telefono = tilTelefono.getEditText().getText().toString(); String correo = tilCorreo.getEditText().getText().toString(); boolean a = esNombreValido(nombre); boolean b = esTelefonoValido(telefono); boolean c = esCorreoValido(correo); if (a && b && c) { // OK, se pasa a la siguiente acción Toast.makeText(this, "Se guarda el registro", Toast.LENGTH_LONG).show(); } } }
[alert-success]Obtén el EditText
del TextInputLayout
con el método getEditText()
.[/alert-success]
Si las tres condiciones se cumplieron, entonces puedes proceder a guardar el registro hipotético.
Paso 4: Añadir Text Watchers a los Edit Texts
Si requieres la comprobación en tiempo real del texto que contiene un EditText
, entonces asigna una escucha TextWatcher
con el método addTextChangedListener()
.
Por ejemplo…
Si quieres que los errores se limpien al momento de escribir una caracter en el campo del nombre, usa setError(null)
en el controlador onTextChanged().
// Referencias ETs campoNombre = (EditText) findViewById(R.id.campo_nombre); campoTelefono = (EditText) findViewById(R.id.campo_telefono); campoCorreo = (EditText) findViewById(R.id.campo_correo); campoNombre.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { tilNombre.setError(null); } @Override public void afterTextChanged(Editable s) { } });
Si quieres comprobar el campo del correo cada vez que se escriba, entonces llama al método de validación en onTextChanged()
.
campoCorreo.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { esCorreoValido(String.valueOf(s)); } @Override public void afterTextChanged(Editable s) { } });
3. Personalizar Etiquetas Flotantes
El elemento TextInputLayout
en su definición XML te permite modificar el comportamiento de las etiquetas flotantes y los errores.
La siguiente tabla muestra algunos de ellos:
Atributo | Descripción |
app:counterEnabled |
Determina si se mostrará un contador de caracteres para el contenido del EditText . Acepta los valores true y false . |
app:errorEnabled |
Habilita o deshabilita la visibilidad de los errores en la parte inferior del EditText . Acepta true y false . También proporciona un espacio en blanco adicional en el layout previsualizado |
app:hintAnimationEnabled |
Habilita o deshabilita la animación de la etiqueta. Si usas false , la etiqueta se moverá bruscamente hacia arriba sin la transición que tiene por defecto. |
app:hintTextAppearance |
Cambia el estilo del texto de la etiqueta flotante. |
android:hint |
Texto auxiliar que se muestra dentro del campo de texto. Puedes declararlo en el TextInputLayout o en el EditText , ambos producen los mismos resultados. |
Veamos algunos ejemplos para personalizar el TextInputLayout
:
Cambiar el color del error — Usa el atributo app:errorTextAppearance
para asignar un estilo propio que extienda de otro que esté prefabricado por el sistema como @android:style/TextAppearance
.
El siguiente estilo asigna un color naranja al error:
styles.xml
<style name="Error" parent="TextAppearance.AppCompat.Caption"> <item name="android:textColor">#FF9800</item> </style>
TIL del nombre
<android.support.design.widget.TextInputLayout android:id="@+id/til_nombre" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_column="1" android:layout_marginLeft="32dp" app:errorTextAppearance="@style/Error" android:layout_row="0">
El resultado:
Cambiar color del hint — Puedes cambiar el color la etiqueta con el atributo app:hintTextAppearance
. Al igual que la apariencia del error, solo extiéndelo de un estilo asociado al texto como TextAppearance.Design.Hint
o TextAppearance.AppCompat.Caption
.
El siguiente ejemplo cambia al color primario la etiqueta:
styles.xml
<style name="Hint" parent="TextAppearance.Design.Hint"> <item name="android:textColor">?attr/colorPrimary</item> </style>
Campo del correo
<android.support.design.widget.TextInputLayout android:id="@+id/til_correo" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_column="1" app:errorEnabled="true" android:layout_marginLeft="32dp" app:hintTextAppearance="@style/Hint" android:layout_row="2">
El resultado será:
Recuerda que la apariencia del texto también incluye su tamaño (textSize
), su formato (textStyle
), etc.
Añadir un contador al TextInputLayout — Si deseas contar los caracteres del text input layout usa los atributos app:counterEnabled
y app:counterMaxLenght
.
El primero habilita la visualización del contador y el segundo establece el límite de caracteres al cual tendrá acceso el usuario. Adicionalmente el input layout cambiará de color el contador y la línea inferior cuando se sobrepase el contador.
Por ejemplo, pongamos un contador con límite de 30 caracteres en el campo del nombre.
<android.support.design.widget.TextInputLayout android:id="@+id/til_nombre" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="32dp" android:orientation="horizontal" app:counterEnabled="true" app:counterMaxLength="30" app:errorEnabled="true">
El resultado sería:
¿Cómo establecer el color del contador en el TextInputLayout?
Lo harás con dos atributos que te otorgan personalización: app:counterTextAppearance
y app:counterOverflowTextAppearance
.
El primero asigna un estilo para el contador mientras la longitud se encuentre en el rango máximo. Y el segundo es para cuando se traspasa dicho límite.
Por ejemplo…
Crearé dos estilos para el contador donde cambiaré los tonos a naranja profundo para el desborde y azul para el estado normal.
styles.xml
<style name="Counter" parent="TextAppearance.Design.Counter"> <item name="android:textColor">#42A5F5</item> </style> <style name="CounterOverFlow" parent="TextAppearance.Design.Counter.Overflow"> <item name="android:textColor">#FF7043</item> </style>
Ahora se los asigno al campo del nombre:
<android.support.design.widget.TextInputLayout android:id="@+id/til_nombre" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="32dp" android:orientation="horizontal" app:counterEnabled="true" app:counterMaxLength="30" app:counterOverflowTextAppearance="@style/CounterOverFlow" app:counterTextAppearance="@style/Counter" app:errorEnabled="true">
Al final tendrías:
Nota: Si quieres jugar más con el estilo del TextInputLayout
, te dejo este link para que pruebes la librería MaterialEditText, donde encontrarás todo tipo de estilos personalizados.
Conclusión
Las etiquetas flotantes son excelentes.
No hay duda que realzan la visualización de los campos de texto y además reducen la complejidad de ubicar un TextView
para determinar la función de un EditText
.
Incluyendo que te permiten setear errores de forma sencilla para que la integridad de los datos del usuario se mantenga y este sea consciente de ella. Esto representa más fiabilidad en tus apps.
Y qué decir sobre la buena cantidad de atributos que posee el TextInputLayout
para personalizar el color de los errores, la etiqueta y la creación de un contador de caracteres.