Objetos Virtuales Del ConstraintLayout

Los objetos virtuales del ConstraintLayout son elementos especiales que representan referencias auxiliares sobre las cuales puedes aplicar restricciones.

Estos no son renderizados en el dispositivo, ya que son usados sólo para propósitos de diseño en tu layout.

Por lo que en este tutorial aprenderás sobre:

  • Líneas de guía
  • Barreras
  • Grupos

Nota: Esta es la cuarta parte de la guía de ConstraintLayout (todo), por lo que te recomiendo leer las anteriores para aprender sobre los fundamentos de este componente.


Ejemplo De Objetos Virtuales Del ConstraintLayout

Los siguientes son tres diseños simples que tomaremos para ver el uso de los objetos de ayuda sobre nuestros layouts:

Objetos virtuales del ConstraintLayout

Puedes descargar el proyecto Android Studio con los archivos XML desde el siguiente botón:


Líneas De Guía (Guidelines)

Usa guías de línea, representadas por la clase Guideline, sirven para trazar una referencia lineal horizontal o vertical, con el fin de aplicar restricciones a tus widgets. Este objeto se extiende por todo el tamaño del ConstraintLayout y se posiciona según la medida que especifiques.

Crear Guideline Desde El Editor De Layouts

Para ver su utilidad, crea un layout llamado p4_guidelines.xml y añade cuatro TextViews.

Existen varias formas de añadir una guideline. La primera de ellas es ir a Pallete>Common>Helpers y arrastrar el componente Guideline (orientación):

La segunda consiste en dar click derecho en el lienzo o el Component Tree y seleccionar Add Helpers>[Orientación] Guideline:

Y la tercera es hacer click en el botón Guidelines desde la toolbar del editor de layouts:

Para ver sus atributos, selecciona la guideline y ve a la ventana Attributes. Aunque aparecen varios elementos para restricciones, modificarlos no tendrá efecto en su contenido:

Con esto en mente, añade una línea de guía vertical y otra horizontal. Tu definición XML se verá similar a esta:

<?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">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/vertical_guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_begin="68dp" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/horizontal_guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_begin="342dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

La etiqueta <Guideline> usará el atributo android:orientation para determinar la orientación de la línea de guía y app:layout_constraintGuide_* para su forma de posicionamiento.

Posicionar Guidelines

Desplaza las líneas de guía haciendo click en los extremos de la línea punteada y arrastrándola sobre el eje:

Posicionar guideline

Hay tres formas distintas de establecer la posición:

  • layout_constraintGuide_begin: Se cuenta la distancia desde el lado izquierdo (horizontal) o superior (vertical)
  • layout_constraintGuide_end: Se cuenta la distancia desde el lado derecho (horizontal) o inferior (vertical)
  • layout_constraintGuide_percent: La posición se establece según un porcentaje del ancho o alto del layout

Cambia entre estos valores haciendo click sobre el indicador de la línea de guía:

Cambiar Constraint Guideline Begin

En nuestro ejemplo usaremos el posicionamiento por porcentaje, ya que deseamos trazar las líneas por el centro del ConstraintLayout sin importar el tamaño de la pantalla:

Conectar Views Con Guidelines

La aplicación de restricciones a los views es exactamente igual. Solo arrastra los conectores y marca la línea de guía como destino.

Conectar views y guidelines

En nuestro ejemplo deseamos marcar los cuadrantes del plano cartesiano a partir del centro, por lo que aplicamos restricciones de posición sobre cada guideline para separar las 4 secciones:

Ejemplo de Guidelines En ConstraintLayout

Las restricciones de los TextViews y el posicionamiento de las guidelines se verían 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/quadrant1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginBottom="16dp"
        android:text="I"
        android:textSize="60sp"
        app:layout_constraintBottom_toTopOf="@+id/horizontal_guideline"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/vertical_guideline"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/quadrant2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginBottom="16dp"
        android:text="II"
        android:textSize="60sp"
        app:layout_constraintBottom_toTopOf="@+id/horizontal_guideline"
        app:layout_constraintEnd_toStartOf="@+id/vertical_guideline"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/quadrant3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginBottom="16dp"
        android:text="III"
        android:textSize="60sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/vertical_guideline"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/horizontal_guideline" />

    <TextView
        android:id="@+id/quadrant4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginBottom="16dp"
        android:text="IV"
        android:textSize="60sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/vertical_guideline"
        app:layout_constraintTop_toTopOf="@+id/horizontal_guideline" />

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

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

</androidx.constraintlayout.widget.ConstraintLayout>

Barreras (Barriers)

Una barrera (Barrier) toma como referencia a múltiples views y crea una línea de guía desde el elemento que ocupe más espacio con respecto a un lado.

Barreras en ConstraintLayout

En la imagen anterior hay dos views y se ha tomado el lado derecho como destino. La barrera es posicionada al extremo derecho del view 1 porque es más ancho.

Si el tamaño de ambos elementos cambia, entonces la barrera se adaptará para avanzar o replegarse según las dimensiones del elemento más extenso. Esto se ve en la segunda imagen donde la barrera se ajusta al view 2 cuando es ampliado.

Crear Barrera Desde El Editor De Layouts

Supongamos que deseamos aplicar una barrera en tres características del detalle de un producto: Nombre, Vendedor y Colores disponibles.

Layout para producto

Crea un layout llamado p4_barriers.xml y añade seis TextViews para las etiquetas de las características y el valor.

Al igual que las líneas de guía, las barreras pueden ser añadidas desde la toolbar del editor, la ventana Pallete y con el menú contextual al hacer click derecho en el lienzo.

Como necesitamos restringir las etiquetas por su lado derecho, añadiremos una barrera vertical al layout:

Android Studio Vertical Barrier

Al crearla la podrás ver en la jerarquía:

Barrier en Component Tree

Añadir Views A La Barrera

Luego sitúate en la ventana Component Tree y arrastra los widgets que deseas hagan parte de la barrera:

Android Studio creará la barrera en el borde por defecto del eje. En este caso, como es una barrera horizontal, será alineado a la izquierda:

Los views que pertenecen a la barrera se añaden en una lista separada por comas desde el atributo app:constraint_referenced_ids:

<androidx.constraintlayout.widget.Barrier
    android:id="@+id/labels_barrier"...
    app:constraint_referenced_ids="colors_label,seller_label,name_label" />

Determinar Dirección De La Barrera

La dirección anterior no es la apropiada. Para cambiarla, establece el valor del atributo barrierDirection en la ventana de atributos. Para nuestro ejemplo será el valor end:

Atributo barrierDirection de Barrier

De esta forma, las etiquetas serán respaldadas por la barrera en la parte derecha:

Conectar Views Con La Barrera

Con la línea punteada definida por la barrera, ahora los textos correspondientes a las etiquetas pueden ser restringidos. Al igual que con las guidelines, solo arrastra líneas de conexión:

El equivalente desde XML es asignar el id de la barrera a la restricción de posicionamiento relativo. Por ejemplo, los atributos app:layout_constraintStart_toEndOf de los textos del producto toman a labels_barrier:

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/name_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="32dp"
        android:text="Nombre"
        android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/seller_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:text="Vendedor"
        android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/name_label" />

    <TextView
        android:id="@+id/colors_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:text="Colores disponibles"
        android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/seller_label" />

    <TextView
        android:id="@+id/name_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:text="Camisa Ask"
        android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
        app:layout_constraintBottom_toBottomOf="@+id/name_label"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/labels_barrier"
        app:layout_constraintTop_toTopOf="@+id/name_label" />

    <TextView
        android:id="@+id/seller_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:text="Moda Extrema"
        android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
        app:layout_constraintBottom_toBottomOf="@+id/seller_label"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/labels_barrier"
        app:layout_constraintTop_toTopOf="@+id/seller_label" />

    <TextView
        android:id="@+id/colors_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:text="Azul, Verde y Blanco"
        android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
        app:layout_constraintBottom_toBottomOf="@+id/colors_label"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/labels_barrier"
        app:layout_constraintTop_toTopOf="@+id/colors_label" />

    <androidx.constraintlayout.widget.Barrier
        android:id="@+id/labels_barrier"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:barrierDirection="end"
        app:constraint_referenced_ids="colors_label,seller_label,name_label" />

</androidx.constraintlayout.widget.ConstraintLayout>

Grupos (Groups)

Por otro lado, los grupos son objetos de ayuda representados por la clase Group, cuyo objetivo es controlar la visibilidad y elevación de múltiples widgets.

En el caso de que un view pertenezca a varios grupos, su visibilidad será definida por aquel grupo que haya sido declarado de último en la definición XML.

Crear Grupos

Supongamos que tenemos un botón que inicia un cálculo hipotético que toma varios segundos. Y mientras se ejecuta se debe mostrar una barra de progreso junto a un texto que decore la intención.

Intercambio de visibilidad en estado de carga de App Android

Para este diseño usaremos un grupo que incluya la ProgressBar y el TextView del estado de carga. De esta forma podremos modificar la visibilidad de ambos, al referirnos al grupo.

Por lo tanto crea un layout llamado p4_grupos.xml y añade los componentes nombrados. De la misma forma que para los objetos anteriores, añade un grupo desde el editor:

Crear grupo en ConstraintLayout

Añadir Views A Un Grupo

Si abres el Component Tree, verás el grupo como parte de la jerarquía. Y como con las barreras, añade los elementos arrastrándolos hacia el:

Añadir widgets a grupo de ConstraintLayout

Esto desde XML, equivale a ir hacia la etiqueta <Group> y modificar el atributo app:constraint_referenced_ids a fin de asignarle una lista con los ids de los miembros.

<androidx.constraintlayout.widget.Group
    android:id="@+id/group"
    android:layout_width="wrap_content"
    android:visibility="gone"
    android:layout_height="wrap_content"
    app:constraint_referenced_ids="progress,empty_text" />

Debido a que la barra de progreso y el texto de carga están escondidos inicialmente, entonces asigna gone al atributo android:visibility.

Modificar Visibilidad Del Grupo

Por último, ve a MainActivity y toma la referencia del botón del cálculo y el grupo:

class MainActivity : AppCompatActivity() {
    private lateinit var calculate: Button
    private lateinit var loadingGroup: Group

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.p4_groups)

        calculate = findViewById(R.id.calculate)
        loadingGroup = findViewById(R.id.group)

        calculate.setOnClickListener {
            calculate()
        }
    }

    private fun calculate() {

    }
}

El método calculate() iniciaremos una corrutina y aplicaremos el método delay() para un retraso de 3 segundos. Al mismo tiempo, cambiaremos la visibilidad del grupo hacia View.VISIBLE y la del botón a View.GONE:

private fun calculate() {
    showLoadingState(true)
    MainScope().launch {
        delay(3000)
        showLoadingState(false)
    }
}

private fun showLoadingState(show: Boolean) {
    loadingGroup.visibility = if (show) View.VISIBLE else View.GONE
    calculate.visibility = if (show) View.GONE else View.VISIBLE
}

Claramente, una vez se termine la tarea, retornaremos las visibilidades a sus valores iniciales con una expresión if.

Ejemplo Group en ConstraintLayout

Cómo ves, el uso de grupos evita que crees un ViewGroup para aplicar visibilidad a múltiples elementos.


Animaciones En El ConstraintLayout

En este tutorial viste el uso de tres de los objetos virtuales más populares con el ConstraintLayout.

Aprendiste que las líneas de guía permiten restringir elementos a partir de trazos posicionados con respecto al layout. Que las barreras trazan límites lineales para múltiples views y que los grupos permiten manejar visibilidades de varios elementos como una unidad.

Vamos a finalizar esta guía viendo cómo construir animaciones a partir del uso de la clase ConstraintSet y el manejador TransitionManager.

Más Contenidos Android

Únete Al Discord De Develou

Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!