Diseño Android: EditText con TextInputLayout y Material Components

Última actualización : 13/12/2020

android

La especificación de Material Design para los campos de texto recoge la utilización de las denominadas “etiquetas flotantes” (floating labels). Este elemento de usabilidad consiste en ubicar las etiquetas de los campos de texto dentro del mismo para posteriormente mostrarlas en la parte superior del campo mientras este tenga el foco o bien contenga algún valor.

El objetivo es optimizar el espacio vertical de la pantalla al mostrarse la etiqueta dentro del campo del texto pero evitar a la vez que el usuario pierda la noción del contenido que corresponde al campo del texto.

Android TextInputLayout

Las recomendaciones de Material Design también aconsejan mostrar los errores de validación debajo del propio campo.

Android TextInputLayout error

Si el campo de texto presenta una limitación en cuanto al número de caracteres que se pueden introducir, se recomienda mostrar un contador de caracteres.

android textinputlayout-character counter

En este tutorial utilizaremos la implementación de este elemento de diseño proporcionada por Google dentro de Material Components For Android (evolución de las antiguas y ya obsoletas librerías de compatibilidad), lo que permite su uso en cualquier versión de Android. Asimismo, el proyecto de ejemplo, para Android 30 y compatible hasta Android 4.4, se apoya en el tutorial “Diseño Android: ActionBar con Toolbar”.

Etiquetas flotantes

En primer lugar, añadimos la librería de Material en el fichero build.gradle del módulo de nuestro proyecto en el que las vamos a utilizar, generalmente el módulo app (fichero completo).

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
}

En el build.gradle general del proyecto nos aseguramos tener el repositorio Maven de Google.

allprojects {
    repositories {
        jcenter()
        google()
    }
}

Ahora vamos a crear una pantalla que mostrará un par de campos de edición de texto. Cada uno de ellos lo vamos a componer de la siguiente manera:

  1. Definimos un elemento de tipo TextInputLayout, responsable de dotar a nuestro campo de texto de la funcionalidad de etiqueta flotante y visualización de errores estilo Material Design
  2. Definir como hijo del TextInputLayout un componente de tipo TextInputEditText, un subtipo del componente EditText creado especialmente para ser utilizado junto a TextInputLayout. También se puede utilizar directamente un EditText lo que resulta especialmente útil si incorporamos TextInputLayout a una aplicación ya existente. En este caso, los logs nos recomendarán migrar a los nuevos TextInputEditText:
    I/TextInputLayout: EditText added is not a TextInputEditText. Please switch to using that class instead.
  3. La etiqueta se define con el atributo android:hint en el TextInputLayout o bien en el TextInputEditText

Cursos aplicaciones móviles

La pantalla del proyecto de ejemplo queda con dos EditText tal y como sigue:

<androidx.constraintlayout.widget.ConstraintLayout 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:orientation="vertical"
              tools:context=".MainActivity">

    <com.google.android.material.appbar.MaterialToolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:elevation="@dimen/elevation_toolbar"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:ignore="UnusedAttribute"/>

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/text_input_layout_email"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/form_margin_top"
            android:layout_marginStart="@dimen/activity_horizontal_margin"
            android:layout_marginEnd="@dimen/activity_horizontal_margin"
            android:hint="@string/email"
            app:layout_constraintTop_toBottomOf="@+id/toolbar">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/editTextEmail"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textEmailAddress"
                />

        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/text_input_layout_pass"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/form_margin_top"
            android:hint="@string/password"
            app:layout_constraintEnd_toEndOf="@+id/text_input_layout_email"
            app:layout_constraintStart_toStartOf="@+id/text_input_layout_email"
            app:layout_constraintTop_toBottomOf="@+id/text_input_layout_email">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/editTextPassword"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textPassword" />

        </com.google.android.material.textfield.TextInputLayout>


</androidx.constraintlayout.widget.ConstraintLayout>

El resultado en acción.

Con esto ya tenemos la funcionalidad de etiqueta flotante en nuestros campos de introducción de texto y ha sido coser y cantar. Se aplican los colores definidos de forma genérica para el tema de Material Components que utiliza toda la aplicación y que es el siguiente:

    <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
        <item name="colorPrimary">@color/primary</item>
        <item name="colorPrimaryDark">@color/primaryDark</item>
        <!--toolbar, primaryColor en tema claro, surfaceColor en tema oscuro-->
        <item name="toolbarStyle">@style/Widget.MaterialComponents.Toolbar.PrimarySurface</item>
    </style>

Tenemos que:

  • El campo de texto tiene un relleno de color colorSurface. Esto es porque por defecto los InputTextlayout son de tipo Filled (relleno). Más adelante veremos el otro tipo disponible (outlined).
  • El colorPrimary se aplica al cursor, la línea inferior y la etiqueta flotante cuando el campo de texto tiene el foco.
  • El texto que introducimos toma el color ?android:textColorPrimary.

En general, el diseño por defecto es bastante bueno y se adapta perfectamente tanto a un tema claro como oscuro aunque en este último caso deberíamos proporcionar como colorPrimary un tono menos saturado.

textinputlayout light and dark theme material component

Los TextInputLayout son personalizables en extremo, y al final de la documentación oficial vienen unas tablas donde se detallan los numerosos atributos que tenemos disponibles por si queremos hacer alguna modificación. A modo de ejemplo, apliquemos el estilo siguiente al campo para el email.

    <style name="AppTheme.TextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.FilledBox">
        <!-- línea inferior -->
        <item name="boxStrokeColor">@color/indigo</item>
        <!-- el color de relleno, (por defecto es colorOnSurface)-->
        <item name="boxBackgroundColor">@android:color/transparent</item>
        <!-- el hint cuando el campo está vacio y sin foco-->
        <item name="android:textColorHint">@color/teal</item>
        <!-- el hint cuando se convierte en etiqueta flotante-->
        <item name="hintTextColor">@color/orange</item>
    </style>
   <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/text_input_layout_email"
            style="@style/AppTheme.TextInputLayout"

Se puede aplicar un estilo personalizado de forma automática para todos los TextInputLayout definiéndolo en el atributo textInputStyle del tema.

 <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
        <item name="colorPrimary">@color/primary</item>
        <item name="colorPrimaryDark">@color/primaryDark</item>
        <!--toolbar, primaryColor en tema claro, surfaceColor en tema oscuro-->
        <item name="toolbarStyle">@style/Widget.MaterialComponents.Toolbar.PrimarySurface</item>
        <!-- aplica un tema personalizado a todos los TextInputLayout de la app-->
        <item name="textInputStyle">@style/AppTheme.TextInputLayout</item>
    </style>

Un detalle sutil pero importante es la existencia de un padding para el contenido del propio EditText con respecto a la línea inferior. En versiones antiguas de Material Design este padding era inexistente y así se comportaba por defecto el componente que lo modelaba.

textinputlayout material components padding

Para volder al diseñor anterior, se puede personalizar el estilo siguiendo este ejemplo en Stackoverflow.

Modo Outlined

Las guías de estilo de Material Design también contemplan la posibilidad de mostrar el cuadro de entrada de texto dentro de una “caja” (modo outlined). Este diseño lo conseguimos simplemente aplicando el estilo adecuado. Por ejemplo, vamos a añadir el siguiente TextInput.

 <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/text_input_layout_outlined"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/form_margin_top"
            android:hint="@string/outlined"
            style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/editTextOutlined"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>

        </com.google.android.material.textfield.TextInputLayout>

Obtendremos este comportamiento.

android TextInputLayout outlined

Iconos

Otra funcionalidad que nos entrega TextInputLayout es la posibilidad de mostrar un icono al principio del campo de texto, algo que nos viene de perlas para nuestro campo email.

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/text_input_layout_email"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/form_margin_top"
            android:layout_marginStart="@dimen/activity_horizontal_margin"
            android:layout_marginEnd="@dimen/activity_horizontal_margin"
            android:hint="@string/email"
            app:startIconDrawable="@drawable/baseline_email_black_24"
            style="@style/AppTheme.TextInputLayout"
            app:layout_constraintTop_toBottomOf="@+id/toolbar">

textinputlayout icon

Errores

EditText proporciona un mecanismo para mostrar errores de validación muy fácil de utilizar ya que basta con indicar el texto del error utilizando el método setError.

EditText Error

Para seguir las recomendaciones de Material Design y mostrar el mensaje de error debajo del EditText hay que utilizar el método setError del TextInputLayout. Sin embargo, para borrar el mensaje de error no basta con invocar a setError(null) puesto que el espacio que ocupa el mensaje de error en pantalla sigue mostrándose. Para solventar esta situación haremos uso del método setErrorEnabled.

En el proyecto de ejemplo se ha añadido un botón para validar los campos mediante el siguiente método.

  public void validate(View view) {
    String mailError = null;
    if (TextUtils.isEmpty(editTextEmail.getText())) {
      mailError = getString(R.string.mandatory);
    }
    toggleTextInputLayoutError(textInputEmail, mailError);

    String passError = null;
    if (TextUtils.isEmpty(editTextPassword.getText())) {
      passError = getString(R.string.mandatory);
    }
    toggleTextInputLayoutError(textInputPassword, passError);

    clearFocus();
  }

  /**
   * Display/hides TextInputLayout error.
   *
   * @param msg the message, or null to hide
   */
  private static void toggleTextInputLayoutError(@NonNull TextInputLayout textInputLayout,
                                                String msg) {
    textInputLayout.setError(msg);
    textInputLayout.setErrorEnabled(msg != null);
  }

textinputlayout error

Se utilizar el color del tema definido para colorError, pero todo el estilo del mensaje de error puede ser personalizado fácilmente. Por ejemplo:

    <!-- mensaje de error debajo del EditText-->
    <style name="AppTheme.EditTextError" parent="Widget.MaterialComponents.TextInputLayout.FilledBox">
        <item name="errorTextColor">@color/orange</item>
    </style>
<com.google.android.material.textfield.TextInputLayout
            android:id="@+id/text_input_layout_pass"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/form_margin_top"
            android:hint="@string/password"
            app:passwordToggleEnabled="true"
            style="@style/AppTheme.EditTextError"
            app:layout_constraintEnd_toEndOf="@+id/text_input_layout_email"
            app:layout_constraintStart_toStartOf="@+id/text_input_layout_email"
            app:layout_constraintTop_toBottomOf="@+id/text_input_layout_email">

Contador de caracteres

Otra funcionalidad interesante de TextInputLayout es el contador de caracteres. Para utilizarlo tenemos que habilitarlo en el TextInputLayout e indicar el número máximo de caracteres permitidos para el campo.

   <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/text_input_layout_char"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/form_margin_top"
            android:hint="@string/text"
            app:counterEnabled="true"
            app:counterMaxLength="10"
            app:layout_constraintEnd_toEndOf="@+id/text_input_layout_email"
            app:layout_constraintStart_toStartOf="@+id/text_input_layout_email"
            app:layout_constraintTop_toBottomOf="@+id/text_input_layout_pass">

Al superarse el límite el campo se mostrará como erróneo.

Conmutador de contraseña

Tal y como recoge Material Design, TextInputLayout nos permite incluir el típico icono pulsable para mostrar\ocultar el texto en los campos de edición de texto de tipo contraseña. Lo configuramos en el TextInputLayout con el siguiente atributo:

app:passwordToggleEnabled="true"

Código de ejemplo

El proyecto completo se encuentra disponible en GitHub. Para más información sobre cómo utilizar GitHub, consultar este artículo.

Master Pyhton, Java, Scala or Ruby

2 comentarios sobre “Diseño Android: EditText con TextInputLayout y Material Components

  1. muy buen aporte me resolto muy beneficioso gracias viejo. una pregunta en su tutorial tiene como habilitar el botn de like y q funcione el contador y segundo q pena, como puedo enviar una copia de un mensaje de contacto al correo del usuario

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios .