ConstraintLayout es posiblemente el layout más utilizado a día de hoy. El porqué es muy sencillo, se trata de un componente muy optimizado que nos da toda la potencia que otros layout no tienen y con un solo anidamiento podremos hacer de todo.

ConstraintLayout 2

Es bastante importante leerse bien este capítulo, porque a partir de ahora y como norma general, cualquier capítulo siguiente utilizará ConstraintLayout. También he quedo esperar a que salga la segunda versión, para poder hablar de algunas de sus características.

A día de hoy, cada vez que creamos un nuevo proyecto viene con las dependencias del ConstraintLayout añadidas, pero si vas a usar un proyecto antiguo, lo primero que deberás hacer es ir a build.gradle (:app) y en la parte de dependencies añadir la siguiente dependencia.

implementation 'androidx.constraintlayout:constraintlayout:2.0.2'

Ahora que ya las tienes en tu proyecto es el momento de empezar.

Estas vistas basan su comportamiento en conectarse unas a otras, es decir, tenemos que decirle a quién se va alinear por la derecha, por la izquierda, por arriba y por abajo.

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context=".MainActivity">

    <View
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@color/colorAccent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Esto sería un ejemplo de lo más sencillo. El padre es un ConstraintLayout, dentro tenemos una view de 100dp x 100dp y un color en el background, esto es básicamente un cuadrado. Debemos fijarnos en los cuatro atributos extra que tiene dicha view.

  • app:layout_constraintTop_toTopOf
  • app:layout_constraintRight_toRightOf
  • app:layout_constraintLeft_toLeftOf
  • app:layout_constraintBottom_toBottomOf

Analicemos alguno. El primer atributo layout_constraintTop_toTopOf está diciendo que nuestra vista se tiene que alinear en la parte superior a la parte superior de otra vista, en este caso el padre, el ConstraintLayout. Da igual el padre que tenga nuestra vista, si le ponemos parent siempre hará referencia a la vista que lo contenga. Nuestra vista anterior se vería así.

ejemplo constraintlayout
Primera vista de ejemplo con ConstraintLayout.

Si volvemos al XML y ponemos vista split o design veremos que hay unas conexiones de nuestra vista a los bordes de la pantalla, eso representa las «cuerdas» que se crean con los atributos anteriores. Es por ello que si ahora nosotros borramos la constraint del botton, nuestra vista irá hacia arriba del todo porque es el único enganche que tiene.

Ejemplo de qué ocurre si borramos el constraint bottom.

Ahora quizás te estés preguntando cual es la forma correcta de alinear una vista, porque si por ejemplo queremos alinear la vista al lateral izquierdo ¿Tenemos que borrar el constrain right? No, tenemos el atributo layout_constraintHorizontal_bias y layout_constraintVertical_bias.

Estos dos atributos nos permiten ajustar de una manera mas exacta la posición horizontal y vertical. Supongamos que tenemos una vista en el centro pero queremos pegado al lateral izquierdo.

    <View
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@color/colorAccent"
        app:layout_constraintHorizontal_bias="0"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

Fíjate que al atributo app:layout_constraintHorizontal_bias le he puesto un 0, ese valor (entre 0 y 1) especifica a que porcentaje lo queremos colocar, y como estamos usando el horizontal, pues le estamos diciendo que si el centro es un 50% lo queremos poner al 0%. Este valor acepta decimales, si por ejemplo quisiéramos ponerlo a un 10% del lateral izquiero, solo tendríamos que ponerle el valor app:layout_constraintHorizontal_bias=»0.10″

Conectando varias vistas en ConstraintLayout

Obviamente el ejemplo de arriba es muy básico y poco real, lo más normal es tener más de una vista. A continuación vamos a ver cómo conectar varias vistas entre si.

En esta ocasión haremos un login básico, pero muy habitual. Contendrá dos editText (usuario y contraseña), un logo (en este caso un cuadrado simulando el logo) y un botón.

Lo primero que haremos será crear el logo, usaré el recuadro anterior, pero esta vez no lo quiero en el centro de la pantalla, lo quiero en la parte superior pero sin tocar. También le he aumentado el tamaño.

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context=".MainActivity">

    <View
        android:layout_width="300dp"
        android:layout_height="80dp"
        android:background="@color/colorAccent"
        app:layout_constraintVertical_bias="0.15"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Hemos centrado una vista al parent y luego le hemos dicho que esté al 15% de la altura, esta será nuestra vista que representa al supuesto icono de cualquier login.

Ahora necesitamos añadir los editText, empezamos con el primero.

  <EditText
        android:id="@+id/etUsername"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:hint="Añade tu email"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/logo"
        app:layout_constraintVertical_bias="0.20" />

Ahora sí que tienes que estar flipando. Pero vamos poco a poco y verás que es más sencillo de lo que parece. Lo primero que hemos hecho ha sido ponerle una id, y no solo a este componente, al logo de antes también. Esto es importante porque las vistas las unimos a través de su id. Si seguimos analizando el componente vemos que tiene un layout_width=»0dp» esto es debido a que queremos que ocupe todo el ancho que tiene disponible, en este caso el del padre pues es a quien está ligado. En otras palabras, con ConstraintLayout, en vez de usar match_parent usamos match_constraint, es decir 0dp.

Fíjate ahora en que uno de los constraint no está fijado al parent. Estamos diciendo que se ajuste la parte superior del componente a la parte inferior del componente con id logo que en este caso es la view que añadimos antes. También tiene un layout_constraintVertical_bias de un 20% para que no esté tan pegado a la otra vista. El resto de atributos los conocemos.

Ahora pondremos la vista de la contraseña, recuerda que esta debe ir por debajo de la anterior, así que su constraint top será layout_constraintTop_toBottomOf ya que vamos a ajustar la parte superior a la inferior de la vista del email.

    <EditText
        android:id="@+id/etPassword"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:hint="Añade la contraseña"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/etUsername"
        app:layout_constraintVertical_bias="0.05" />

Este es muy parecido al anterior, nos enganchamos a la vista superior y a la parte inferior del padre y ponemos un vertical_bias de 0.05 para separarlo un poco de la vista anterior.

Ya solo nos quedaría el botón.

    <Button
        android:id="@+id/btnLogin"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:text="Login"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/etPassword"
        app:layout_constraintVertical_bias="0.95" />

Y otra vez hemos hecho un comportamiento casi idéntico en el cual la única diferencia es que el vertical_bias es casi el 100% para que esté pegado a la parte inferior.

Login diseñado con ConstraintLayout curso de kotlin
Login diseñado con ConstraintLayout

La vista entera quedaría así.

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context=".MainActivity">

    <View
        android:id="@+id/logo"
        android:layout_width="300dp"
        android:layout_height="80dp"
        android:background="@color/colorAccent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.15" />

    <EditText
        android:id="@+id/etUsername"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:hint="Añade tu email"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/logo"
        app:layout_constraintVertical_bias="0.20" />

    <EditText
        android:id="@+id/etPassword"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:hint="Añade la contraseña"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/etUsername"
        app:layout_constraintVertical_bias="0.05" />

    <Button
        android:id="@+id/btnLogin"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:text="Login"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/etPassword"
        app:layout_constraintVertical_bias="0.95" />

</androidx.constraintlayout.widget.ConstraintLayout>

Chains (cadenas) en ConstrainLayout

Las cadenas es una de las cosas más cómodas de las ConstraintLayout, Imaginamos que tenemos tres botones horizontalmente, cada uno ligado al otro. ¿Cómo deberían comportarse? Pues con las cadenas podemos definir ese comportamiento.

Para conectar varias vistas horizontales (o verticales) el comportamiento es muy sencillo, solo debemos decirle que nuestra vista empieza donde termina la anterior. El código con tres botones sería este.

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="btn1"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/btn2"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="btn2"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/btn3"
        app:layout_constraintStart_toEndOf="@id/btn1"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="btn3"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/btn2"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Si te fijas la única diferencia entre ellos es que voy poniendo que se ajusten al final del anterior y al principio del siguiente. Si miramos el diseño veremos que cada uno se ajusta y coge el tamaño necesario y se alinea con los otros. Pero podemos hacer más. Para ello tenemos las chain o cadenas.

Este atributo nos permite seleccionar cómo va a hacerse la agrupación cuando tenemos una serie de vistas en fila (da igual que sea vertical u horizontal). Esto lo haremos a través del atributo layout_constraintHorizontal_chainStyle o layout_constraintVertical_chainStyle dependiendo obviamente si es una fila vertical u horizontal, también debes saber que no hay que ponérselo a todos los componentes, solo al primero.

Esta propiedad la podremos modificar de cuatro modos distintos.

  • Spread: Es el valor determinado si no añadimos ninguno. Lo que hará será separar las vistas uniformemente.
  • Spread inside: La primera y la última vista se van a los extremos, si por ejemplo podemos coger todo el ancho se irán a los bordes y el resto de vistas se separarán uniformemente.
  • Packed: Se agrupan todas en el centro de la vista.
  • Weighted: Esta no es una propiedad tomo tal, pero se activa a seleccionar spread o spread inside y darle un valor distinto a la anchura o altura de uno de los componentes. Por ejemplo ponerle 0dp a un botón y a los demás wrap_content, esto hará que el que tiene 0dp (match_constraint) ocupe el máximo restante de los dos botones.
tipos de chains con constraint layout
Los cuatro tipos de chain disponibles en ConstrainLayout.

Guidelines

Las guidelines son una ayuda extra para ajustar nuestra vista de la forma más óptima. Básicamente son componentes invisibles para poder ajustar nuestras vistas a ellos.

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.5" />

Tan sencillo como esto, si te fijas solo hay dos atributos nuevos. Empezamos con android:orientation=»vertical» que definirá la orientación del guideline, que puede ser vertical u horizontal. El segundo atributo sería app:layout_constraintGuide_percent=»0.5″ que nos ayudará a colocar nuestra guideline en la posición exacta de la vista, en este caso si trazaramos una línea vertical en nuestro layout, eso sería nuestro guideline y como está a 0.5 se ajustaría al 50 por ciento de la vista.

Representación visual de una guideline.

Podemos usar infinitas guidelines, siempre con cabeza claro está. Para finalizar voy a dejar un ejemplo algo más loco para que veamos un caso práctico.

Imaginemos este absurdo caso, quiero mostrar un texto en una porción muy exacta de mi app, obviamente cada móvil tiene una pantalla distinta y queremos que siempre esté en el mismo lugar así que tendremos que usar porcentajes.

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context=".MainActivity">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guidelineStart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.2" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guidelineEnd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.4" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guidelineTop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.2" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guidelineBottom"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.4" />
    
    <TextView
        android:id="@+id/tvExample"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:textSize="21sp"
        android:text="Esto es un ejemplo de un texto con guidelines"
        app:layout_constraintTop_toTopOf="@id/guidelineTop"
        app:layout_constraintBottom_toBottomOf="@id/guidelineBottom"
        app:layout_constraintStart_toStartOf="@id/guidelineStart"
        app:layout_constraintEnd_toEndOf="@id/guidelineEnd"/>

</androidx.constraintlayout.widget.ConstraintLayout>

En este retorcido ejemplo he creado una especie de rectángulos con las guideline, y le he dicho a mi textView que solo se ajuste a dentro (he puesto 0dp porque quiero que ocupe todo el tamaño del cual me he conectado) y he ampliado el tamaño del texto para que veamos realmente hemos limitado el espacio del textView y que llega un momento que dejamos de ver el texto porque se saldría de nuestra área.

TextView entre cuatro guidelines.
TextView entre cuatro guidelines.

Espero que con esto tengas las bases necesarias para poder trabajar con ConstraintLayout a partir de ahora. Como siempre recuerda que si tienes alguna duda las puedes dejar aquí o en los comentarios del vídeo.

También te recomiendo ver los otros capítulos de diseño de layout para estar siempre a la última.

Siguiente curso: Jetpack Compose


Te recuerdo que puedes seguirme en mis redes sociales en Aristi.Dev. Y si tienes dudas con este o cualquier otro artículo del blog únete al Discord de la comunidad y te ayudaremos.