En el capítulo anterior aprendimos que era un  componente personalizado y realizamos uno muy sencillo. Esta vez complicaremos un poco la cosa, creando un componente que contiene varios componentes en su interior, al contrario que en el capítulo 18, que extendía directamente de un editText.

El componente que vamos a crear, será un editText  en el cual pondremos un mail, mientras el usuario lo escribe, se irá validando que tiene un formato de correo (nombre, arroba, nombre, punto y dominio) y hasta que no lo valide, aparecerá un mensaje de error debajo y la barra inferior tendrá un color de error.

Lo primero que haremos será crear una clase llamada EmailValidatorView, esta clase será el componente que crearemos, e implementará un RelativeLayout que será la vista que va a contener nuestro componente.

class EmailValidatorView(context: Context, attrs: AttributeSet) : RelativeLayout(context, attrs) {

El relativeLayout contiene un constructor, pues necesitará un context y un AttributeSet. Con este último lo que vamos a conseguir es que nuestro componente pueda ser rellenado desde el xml, es decir, tendremos atributos nuevos para añadir.

Para poder hacer esto, iremos a app/src/main/res/values/ y crearemos un fichero llamado attrs.

Este fichero contendrá los parámetros que le podremos añadir a nuestro componente.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="PasswordValidator">
        <attr name="underlineErrorColor" format="color"/>
        <attr name="underlineSuccessColor" format="color"/>
        <attr name="textError" format="string"/>
    </declare-styleable>
</resources>

Si nos fijamos en el fichero anterior, contiene un styleable de nombre MailValidator, y con tiene tres atributos.

  • underlineErrorColor: Le podremos pasar un color para que la barra inferior del editText se tiña mientras el mail no es válido.
  • underlineSuccessColor: Color para la barra inferior cuando el mail esté validado.
  • textError: Será el mensaje que aparece mientras no se cumpla la validación.

Además del nombre, contienen un format, que le dice al sistema qué tipo de valor espera. En este caso serán dos colores y una String, pero tenemos los siguientes tipos disponibles.

  • color
  • reference
  • boolean
  • dimension
  • enum
  • flag
  • float
  • fraction
  • integer
  • string

Ahora, si creamos un componente EmailValidatorView, podremos añadir los parámetros anteriormente mencionados.

<com.cursokotlin.customviewkotlin.EmailValidatorView
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     app:textError="Introduzca un email válido"
     app:underlineErrorColor="@color/red"
     app:underlineSuccessColor="@color/green"/>

Es posible que nos aparezcan en rojo, pues tendremos que importar en el padre lo siguiente

xmlns:app="http://schemas.android.com/apk/res-auto"

Aunque el IDE nos permitirá importarlo automáticamente pulsando el comando indicado (en mi caso, como uso mac, es option + enter).

Mensaje de error si no has importado el schema.

Los colores redgreen han sido añadidos en el fichero colors.xml.

Lo siguiente será crear el diseño del componente. Este será muy sencillo, contendrá un editText y un textView. Lo llamaremos email_validator.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/etMail"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="textEmailAddress" />

    <TextView
        android:id="@+id/tvErrorCode"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/etMail"
        android:textColor="@color/red"
        android:visibility="invisible" />

</RelativeLayout>

Si nos fijamos en el ejemplo anterior, hemos añadido un inputType al editText, con eso le decimos que si el usuario hace clic en él, debemos mostrar un teclado acorde para añadir un email.

En el textView lo único reseñable será que es de color rojo y empezará invisible, para que no se muestre hasta que el usuario empiece a interactuar con él.

Ahora debemos recuperar los valores que hemos añadido, para ello crearemos un constructor init{}  en nuestro componente.

var successColor: Int
var errorColor: Int

init {
    inflate(context, R.layout.email_validator, this)
    val attributes = context.obtainStyledAttributes(attrs, R.styleable.MailValidator)
    tvErrorCode.text = attributes.getString(R.styleable.MailValidator_textError)
    errorColor = attributes.getColor(R.styleable.MailValidator_underlineErrorColor, ContextCompat.getColor(context, R.color.colorAccent))
    successColor = attributes.getColor(R.styleable.MailValidator_underlineSuccessColor, ContextCompat.getColor(context, R.color.colorAccent))
    attributes.recycle()
}

Analicemos el código anterior.

Hemos declarado dos variables de clase, successColorerrorColor, que usaremos para almacenar los colores del componente y para mostrarlos dependiendo el estado de la validación. A continuación inflamos la vista con el layout que hemos creado.

Ahora es el momento de recuperar los atributos llamaremos a la función obtainStyleAttributes(), donde le pasamos el attributeSet de la clase y la ruta donde hemos creado el styleable. Con esto tendremos un objeto attributes el cual nos podrá dar todos los valores que hemos guardado, recordad que depende del formato que hallamos puesto en el styleable MailValidator tendremos que llamar a attributes.getString(), attibutes.getColor(), etc.

Una vez recuperemos todos los valores, habrá que llamar a la función recycle().

El ultimo paso será implementar un TextWatcher que nos ayudará a comprobar el texto que va añadiendo el usuario a tiempo real.

Para ello tendremos que implementarlo.

class EmailValidatorView(context: Context, attrs: AttributeSet) : RelativeLayout(context, attrs), TextWatcher {

Al implementarlo nos saldrá un error.

Error por no haber implementado los métodos necesarios.

Como en otros capítulos lo que debemos hacer es añadir dichos métodos, serán tres:

  • afterTextChange()
  • beforeTextChange()
  • onTextChanged()

No los volveré a explicar, porque he hablado de ellos en el capítulo anterior.

Ahora tenemos que decir que texto es el que queremos que «escuche» para que nos vaya avisando del cambio. Al final del init{} añadiremos el listener al editText.

etMail.addTextChangedListener(this)

Con esto ya estará listo nuestro editText, pero tendremos que hacer la validación del email, para ello usaremos una expresión regular.

Las expresiones regulares son una serie de normas para la validación de un texto, podríamos entrar en detalle pero se alargaría mucho. Podéis leer más sobre ellas aquí. Puede costar un poco de entender, lo bueno es que la mayoría de validaciones ya están colgadas por la red y no será difícil encontrar lo que necesitemos. En este caso, la validación para el email sería esta: «\b[\w.%-]+@[-.\w]+\.[A-Za-z]{2,4}\b«. Con ella le decimos que queremos texto, un arroba, más texto, un punto, y entre dos y cuatro letras más.

Iremos a la función onTextChanged() y validaremos el texto que vaya introduciendo el usuario.

val pattern = Pattern.compile("\\b[\\w.%-]+@[-.\\w]+\\.[A-Za-z]{2,4}\\b") 
val matcher = pattern.matcher(s.toString()) 
val valid = matcher.matches()

La primera línea creará la expresión regular, la segunda validará el texto que ha introducido el usuario, y la variable valid nos dirá si cumple los requisitos o no.

Ahora realizaremos las acciones necesarias dependiendo del estado, es decir, si el usuario ha validado correctamente o no.

if (valid) {
    tvErrorCode.visibility = View.INVISIBLE
    etMail.background.setColorFilter(successColor, PorterDuff.Mode.SRC_IN)
} else {
    tvErrorCode.visibility = View.VISIBLE
    etMail.background.setColorFilter(errorColor, PorterDuff.Mode.SRC_IN)
}

Con esto cambiaremos el color de la línea inferior del editText y mostraremos el texto de validación.

La clase entera quedaría así.

class EmailValidatorView(context: Context, attrs: AttributeSet) : RelativeLayout(context, attrs), TextWatcher {


    var successColor: Int
    var errorColor: Int

    init {
        inflate(context, R.layout.email_validator, this)
        val attributes = context.obtainStyledAttributes(attrs, R.styleable.MailValidator)
        tvErrorCode.text = attributes.getString(R.styleable.MailValidator_textError)
        errorColor = attributes.getColor(R.styleable.MailValidator_underlineErrorColor, ContextCompat.getColor(context, R.color.colorAccent))
        successColor = attributes.getColor(R.styleable.MailValidator_underlineSuccessColor, ContextCompat.getColor(context, R.color.colorAccent))
        attributes.recycle()
        etMail.addTextChangedListener(this)
    }

    override fun afterTextChanged(s: Editable?) {
    }

    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {


        val pattern = Pattern.compile("\\b[\\w.%-]+@[-.\\w]+\\.[A-Za-z]{2,4}\\b")
        val matcher = pattern.matcher(s.toString())
        val valid = matcher.matches()
        if (valid) {
            tvErrorCode.visibility = View.INVISIBLE
            etMail.background.setColorFilter(successColor, PorterDuff.Mode.SRC_IN)
        } else {
            tvErrorCode.visibility = View.VISIBLE
            etMail.background.setColorFilter(errorColor, PorterDuff.Mode.SRC_IN)
        }
    }
}

El resultado sería el siguiente.

Componente funcionando.

Puedes descargar el proyecto completo desde mi GitHub.

Continúa con el curso: Capítulo 20 – Consumiendo APIS con Retrofit 2