Restricciones Del ConstraintLayout En Android

En este tutorial te explicaré el uso de más básicas restricciones del ConstraintLayout como lo son: restricciones de posicionamiento relativo, de márgenes, de centrado y de dimensión.

La idea es comprender los atributos que representan estas restricciones y cómo podemos aplicarlos desde el editor de layouts en Android Studio.


Ejemplo De Restricciones Del ConstraintLayout

Tomaremos como ilustración el layout que diseñamos en el tutorial anterior sobre el detalle de un tutorial de Develou, para mejorar el aspecto con las restricciones que veremos:

Ejemplo de restricciones básicas de un ConstraintLayout

Descarga el código del proyecto Android Studio desde el siguiente enlace. El layout nuevo tendrá el nombre de p2_restricciones.xml.


Posicionamiento Relativo

Las restricciones de posicionamiento relativo te permiten posicionar un view a partir de una declaración que haga referencia a otro view o al padre.

Como viste en el anterior tutorial, es posible restringir vertical y horizontalmente, lo que permite plena libertad de enlazar cualquier lado de un widget con el de otro.

Añadir Restricción De Posicionamiento

Para añadir una restricción de posicionamiento, selecciona el view desde la pestaña Design. Luego haz click en un círculo de restricción y arrastra hacia un punto de anclaje de otro view para establecer la conexión:

Añadir restricción en ConstraintLayout

Otra forma de hacerlo es seleccionar el view y luego ir a la sección Layout de la ventana Attributes. Allí verás representados sus límites y las restricciones existentes. Si haces click en los botones que no tienen restricciones, Android Studio creará automáticamente la conexión con el widget más cercano:

Restricción de posición relativo desde editor de layout

Eliminar Restricción De Posicionamiento

Ahora bien, con la misma mecánica se realiza la eliminación de la restricción. Desde el lienzo selecciona el view y luego la restricción. En seguida presiona tu tecla Supr (Delete).

Otra forma es dar click en el punto que indica la existencia de la restricción desde la sección Layout:

También puedes presionar Click + Ctrl (Comand en MacOS) para eliminar la restricción directamente:

Eliminar restricción con Ctrl+clic

Posicionamiento Con Respecto Al Padre

Si deseas que él view se condicione a su padre, entonces usa el valor parent en la restricción específica:

El ejemplo anterior muestra cómo alinear los bordes superior, izquierdo y derecho de un Button, con el borde superior del ConstraintLayout que lo contiene. En XML esta definición sería:

<Button
    android:id="@+id/button" ...
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

Los valores posibles son:

  • layout_constraintLeft_toLeftOf
  • layout_constraintLeft_toRightOf
  • layout_constraintRight_toLeftOf
  • layout_constraintRight_toRightOf
  • layout_constraintTop_toTopOf
  • layout_constraintTop_toBottomOf
  • layout_constraintBottom_toTopOf
  • layout_constraintBottom_toBottomOf
  • layout_constraintBaseline_toBaselineOf
  • layout_constraintStart_toEndOf
  • layout_constraintStart_toStartOf
  • layout_constraintEnd_toStartOf
  • layout_constraintEnd_toEndOf

El primer lado pertenece al view de origen y el segundo al del view objetivo. Por lo que expresar app:layout_constraintEnd_toEndOf="parent" se traduce a «el lado derecho de este botón se vincula al lado derecho del padre».

Nota: Los enunciados Start y End corresponden al lado izquierdo y derecho del view sin importar el tipo de lenguaje. Por lo que será de utilidad para cubrir configuraciones de idiomas que se escriben de derecha a izquierda.

Posicionamiento Con Respecto A Otro View

Para este caso la restricción toma el valor del atributo android:id del view que se desea restringir con respecto a un lado. Esto facilita el ordenamiento en que son desplegados los elementos:

Posicionamiento Con Respecto A Otro View

La imagen anterior muestra el uso de la restricción app:layout_constraintStart_toEndOf="@+id/button" para posicionar el lado derecho de View 2 con el lado izquierdo de View 1:

<Button
    android:id="@+id/button2" ...
    app:layout_constraintStart_toEndOf="@+id/button" />

Alineación De Views

Supongamos que necesitamos alinear el lado superior de un view con el lado superior de otro. Para cumplir este cometido solo arrastra la restricción para que coincida con los lados de alineación:

O también el botón de alineación (Align) en la toolbar del ConstraintLayout. Seleccionas los views que serán alineados y luego seleccionas el borde de alineación. En este ejemplo es Top Edges:

Alineación

Estas mismas opciones las puedes encontrar en el menú contextual que se despliega al hacer click derecho en los views:

Alinear views en ConstraintLayout con Align > Top Edges

Usa los atributos que contienen al mismo lado tanto de origen como de objetivo, para alinear los bordes de los widgets. El ejemplo anterior es representado por la siguiente definición XML:

<Button
    android:id="@+id/button2" ...
    app:layout_constraintTop_toTopOf="@+id/button" />

Como es el borde superior, será Top_toTop.

Alineación Por Línea Base Del Texto

La línea base del texto o baseline es la línea de referencia donde la mayoría de letras se sostienen y aquellos segmentos descendientes de letras como p, q o g, se extienden.

Línea de base en ConstraintLayout

Si deseas alinear views por su baseline, entonces haz click derecho en el view y selecciona Show Baseline. Esto desplegará la línea de base y será apta para realizar conexiones con otras:

Alinear views por baseline ConstraintLayout

El atributo asociado para la restricción es layout_constraintBaseline_toBaselineOf y al igual que las demás restricciones, recibe el id del view para alinear:

<Button
    android:id="@+id/button2" ...
    app:layout_constraintBaseline_toBaselineOf="@+id/button" />

Igualmente que las otras alineaciones, puedes alinear por líneas de base seleccionando Align>Baselines.


Agregar Márgenes

Especifica las márgenes de cada view para añadir espacio entre los views conectados a partir de restricciones:

Usa la ventana Attributes para editar los valores de las márgenes de un view a partir de:

  • La escritura de un valor numérico mayor o igual a cero
  • La selección de márgenes estándar en el menú desplegable
  • La selección de un recurso de dimensiones con @ ...
Margenes con Attributes > Layout

Asimismo usa el botón Default Margins en la toolbar para establecer el valor de las márgenes por defecto, cuando un view es agregado:

Los atributos asociados a las márgenes de cada lado son los siguientes:

  • android:layout_marginStart
  • android:layout_marginEnd
  • android:layout_marginLeft
  • android:layout_marginTop
  • android:layout_marginRight
  • android:layout_marginBottom
  • layout_marginBaseline

Por ejemplo, la margen de 136dp a la derecha con respecto al padre mostrada anteriormente se implementa así:

<Button
    android:id="@+id/button2" ...
    android:layout_marginEnd="136dp"
    android:layout_marginRight="136dp" />

Restricciones De Centrado Y Sesgo

Cuando aplicas las dos restricciones para un mismo eje, el ConstraintLayout centrará automáticamente el view con respecto a los bordes de destino:

Centrar view en padre ConstraintLayout

El ejemplo anterior centra horizontalmente el view, por lo que su definición XML usa a :

<Button
    android:id="@+id/button" ...
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent" />

Ajustar Restricción De Sesgo

El bias o sesgo es una restricción que entra en acción cuando limitas ambos lados de un eje. Aunque en el centrado no se especifica el valor de este, su valor por defecto es 50% del espacio entre los orígenes de conexión.

Su objetivo es determinar la cantidad de desplazamiento que el view recorrerá en el eje acotado por restricciones.

Por ejemplo, si quisieras aplicar el 30% hacia la izquierda en vez del 50%, entonces puedes ir a la ventana Attributes y arrastrar el slider a dicho valor. O también arrastrar al view entre los muelles de acotación:

Bias de ConstraintLayout

El sesgo es representado por los atributos layout_constraintHorizontal_bias para el eje horizontal y layout_constraintVertical_bias para el vertical. Ambos reciben un String con formato decimal para valores del rango [0.0, 1.0]:

<Button
    android:id="@+id/button" ...
    app:layout_constraintHorizontal_bias="0.3" />

Restricciones De Dimensiones

Al igual que en otros layouts, las dimensiones de los widgets dentro del ContraintLayout son especificadas por los atributos android:width y android:height. Los siguientes son los posibles valores aceptados para modificar la forma en que el espacio es calculado.

Dimensión Específica

Aquí las dimensiones se representan por valores fijos en dp o por un recurso de dimensión. Por ejemplo, un ancho y alto de 50dp x 50dp:

Dimensiones fijas en ConstraintLayout

También es posible hacer esta modificación desde la ventana Attributes si haces click en el control linear que se encuentra al interior del contorno:

Dimensiones fijas Attributes > Layout

WRAP_CONTENT

Este valor cumple el mismo cometido que en los otros layouts. Le permite al view ajustar su tamaño según las dimensiones del contenido.

Por ejemplo, si un TextView tiene el String «ABC» su tamaño será menor que si tiene «ABC DEF», ya que calcula su ancho basado en el contenido:

WRAP_CONTENT en ConstraintLayout

Aplica este valor desde Attributes modificando el controlador con el siguiente icono:

Attributes > Layout wrap_content

MATCH_CONSTRAINT

Con este valor las dimensiones del view son expandidas hasta ocupar todo el espacio restante que sus restricciones le posibilitan.

Por ejemplo, si quisiéramos que el TextView ocupe todo el espacio horizontal sin importar su contenido:

match_constraint en ConstraintLayout

Desde el editor aplicas este valor llevando la línea de dimensión al siguiente estado:

Attributes>Layout match_constraint

En el formato XML verás que MATCH_CONSTRAINT es equivalente a usar el valor 0dp en la dimensión:

<TextView
    android:id="@+id/text1"
    android:layout_width="0dp"
    android:layout_height="wrap_content".../>

Ratio

Si deseas establecer las dimensiones de un view a partir de la relación de aspecto (la razón entre su ancho y alto) usa el atributo app:layout_constraintDimensionRatio. O presiona la esquina superior izquierda Toggle Aspecto Ratio Constraint para revelar un campo de texto para especificar el ratio.

Esta restricción ocupará todo el espacio disponible que las restricciones le permitan y además mantendrá la relación de aspecto.

Su uso estará disponible cuando alguna dimensión tiene MATCH_CONSTRAINT. Los valores pueden ser números flotantes o relaciones ancho:alto (siendo 1:1 el valor inicial). Por ejemplo, un ImageView con un aspecto de proporción vertical 16:9:

En el código verás el atributo app:layout_constraintDimensionRation con el valor del par de relación acompañado de una

<ImageView
    android:id="@+id/imageView"
    android:layout_width="0dp"
    android:layout_height="wrap_content"...
    app:layout_constraintDimensionRatio="w,16:9" />

Si quieres restringir una de las dos dimensiones usa las letras w para el ancho o h para el alto, separado por una coma con el aspecto.


Aplicando Restricciones

Teniendo en cuenta el conocimiento previo, pasemos a mejorar el layout de ejemplo que trabajamos.

Estos es:

  • Aplicar márgenes de 16 dp para todas las restricciones, excepto el vínculo del autor con la fecha de publicación
  • Usar todo el espacio disponible para los tamaños de la imagen destacada, el título y el contenido del post
  • Poner el sesgo horizontal del título del post en 0.0
  • Usar una aspecto de relación 16:9 para a imagen destacada y fijar su altura en 232dp

La definición XML final sería:

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

    <ImageView
        android:id="@+id/post_featured_image"
        android:layout_width="0dp"
        android:layout_height="232dp"
        android:contentDescription="@string/featured_image_desc"
        android:src="@drawable/featured_image"
        app:layout_constraintDimensionRatio="w,16:9"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/post_title"
        android:layout_width="0dp"
        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:text="Añadir Un ConstraintLayout En Android Studio"
        android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/post_featured_image" />

    <TextView
        android:id="@+id/post_author"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:text="James Revelo"
        android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
        android:textStyle="bold"
        app:layout_constraintEnd_toStartOf="@+id/post_category"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/post_title" />

    <TextView
        android:id="@+id/post_publish_date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="22/07/2021"
        android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
        app:layout_constraintStart_toStartOf="@+id/post_author"
        app:layout_constraintTop_toBottomOf="@+id/post_author" />

    <TextView
        android:id="@+id/post_category"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:gravity="center"
        android:text="UI"
        android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
        app:drawableLeftCompat="@drawable/ic_brush"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/post_title" />

    <TextView
        android:id="@+id/post_content"
        android:layout_width="0dp"
        android:layout_height="0dp"
        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:scrollbars="vertical"
        android:text="@string/text_content"
        android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
        app:layout_constraintBottom_toTopOf="@+id/next_button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/post_publish_date" />

    <Button
        android:id="@+id/previous_button"
        style="@style/Widget.MaterialComponents.Button.TextButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="16dp"
        android:text="Anterior"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/next_button"
        style="@style/Widget.MaterialComponents.Button.TextButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginBottom="16dp"
        android:text="Siguiente"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

En la previa podrás observar la materialización de todas las restricciones existentes:

Ejemplo de restricciones de ConstraintLayout

Cadenas En Un ConstraintLayout

En este tutorial aprendiste el uso de las restricciones del ConstraintLayout para establecer posiciones, márgenes, centrados, sesgos y dimensiones.

Lo siguiente será abordar el uso de cadenas. Estas son restricciones que vinculan grupos de views, con el fin de restringir su relación bidireccional (horizontal o vertical) y aplicar distribución de espacios.


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!