Las cadenas en el ConstraintLayout
, son restricciones que permiten distribuir linealmente un grupo de views que están conectados bidireccionalmente.
Las restricciones de posición aplicadas al view en la cadena, son independientes del otro eje. Esto quiere decir que si un widget pertenece a una cadena vertical, no afectará la distribución de una cadena horizontal de la que también forme parte.
El objetivo de este tutorial es mostrarte como:
- Crear una cadena (Chain) en Android Studio
- Diferenciar los estilos de cadenas
- Conocer los atributos del
ConstraintLayout
para cadenas - Saber cómo influyen las márgenes en las cadenas
Nota: Este tutorial es la tercera parte de la guía del ConstraintLayout en Android (todo). Te recomiendo leer los contenidos Añadir ConstraintLayout y Restricciones para seguir un orden secuencial y comprender conocimientos previos.
Ejemplo De Cadenas En El ConstraintLayout
Usaremos como ilustración la creación de un layout básico de una calculadora, cuya naturaleza pone a prueba la distribución de botones para números y operaciones:
Con este ejemplo podrás practicar a fondo el uso de cadenas, con el fin de crear una cuadrícula con diferentes dimensiones y alineaciones. Puedes descargar el ejemplo desde el siguiente botón (el layout final se llama p3_cadenas.xml
):
Crear Cadenas
Para explorar la creación de cadenas, primero crearemos un nuevo recurso de layout llamado p3_cadenas.xml
con un elemento raíz del tipo ConstraintLayout
:
Y luego añadiremos tres botones para representar la fila del teclado con los números 1, 2 y 3:
Crear Cadena Horizontal
Ahora bien, para crear una cadena horizontal desde el editor de layouts en Android Studio, selecciona los views que harán parte de ella, luego haz click derecho en alguno de ellos y selecciona la opción Chains>Create Horizontal Chain:
Android Studio añadirá automáticamente dos extremos para el primer y último view con el fin de establecer el eje. Y luego añadirá el vínculo entre cada eslabón. La cadena no alineará el grupo, por lo que usa las restricciones de alineación para conseguir este efecto.
Los enlaces de las cadenas se representan por el vínculo bidireccional entre los botones. En XML esto se traduce al uso de las restricciones de posicionamiento en ambos sentidos.
Por ejemplo, el uso de End_toStart
y Start_toEnd
entre el botón 1 y el 2:
<Button
android:id="@+id/button"...
app:layout_constraintEnd_toStartOf="@+id/button2"/>
<Button
android:id="@+id/button2"...
app:layout_constraintStart_toEndOf="@+id/button"/>
Con esto en mente, añade los botones para las siguientes filas de botones y crea las cadenas horizontales correspondientes:
Crear Cadena Vertical
Crear una cadena vertical es exactamente igual. Selecciona los views que serán distribuidos verticalmente, pero esta vez selecciona la opción Create Vertical Chain. Por ejemplo, si conectamos verticalmente a los botones 7, 4, 1 y 0:
Cabezas De Cadenas
Al primer elemento de una cadena se le denomina cabeza. Este alberga un conjunto de atributos que hacen control de los demás eslabones en la cadena.
La cabeza en una cadena horizontal es el elemento más a la izquierda del grupo y en una vertical, será el elemento en la parte superior.
Por esta razón en la imagen anterior, el botón 4 es la cabeza de la cadena horizontal y el botón 7 el de la vertical.
Estilos De Cadenas
Es posible cambiar el comportamiento de distribución de espacio entre los elementos de una cadena a partir de la aplicación de los siguientes estilos:
En XML los estilos se asignan a los atributos app:layout_constraintHorizontal_chainStyle
y app:layout_constraintVertical_chainStyle
.
Estilo Extendido (CHAIN_SPREAD)
Es el estilo por defecto cuando creas una cadena. Con él, los eslabones se despliegan uniformemente sobre el espacio disponible.
Aplícalo seleccionando la cadena y luego yendo a Chains > * Chain > spread:
Usa el valor spread en el atributo de estilo. En la ilustración anterior, Android Studio agregaría el valor a la definición horizontal:
<Button
android:id="@+id/button1"...
app:layout_constraintHorizontal_chainStyle="spread" />
Estilo Extendido En El Interior (CHAIN_SPREAD_INSIDE)
Es igual que la cadena extendida normal, solo que los extremos de la cadena no se esparcen. Se representa por la constante CHAIN_SPREAD_INSIDE
en Kotlin y spread_inside
en XML.
Estilo Ponderado (Weitghted Chain)
Si la cabeza está usando los modos CHAIN_SPREAD
o CHAIN_SPREAD_INSIDE
y las dimensiones de los views tienen a MATCH_CONSTRAINT
. Es posible manipular el espacio que ocupará el elemento a partir de ponderaciones (pesos).
Este concepto es similar al atributo android:weight
del LinearLayout
, donde el espacio es distribuido según la proporción de cada view.
En la ilustración inicial le dimos pesos de 1, 2 y 3 respectivamente a cada botón. Por esta razón el botón 3 ocupa el 50% del espacio disponible, ya que 3 es la mitad del peso total 6.
<?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">
<Button
android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="1"
android:visibility="visible"
app:layout_constraintEnd_toStartOf="@+id/button2"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="2"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintEnd_toStartOf="@+id/button3"
app:layout_constraintStart_toEndOf="@+id/button" />
<Button
android:id="@+id/button3"
android:layout_width="0dp"
app:layout_constraintHorizontal_weight="3"
android:layout_height="wrap_content"
android:text="3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/button2" />
</androidx.constraintlayout.widget.ConstraintLayout>
Asigna el peso con el atributo app:layout_constraintHorizontal_weight
o Asigna el peso con el atributo app:layout_constraintVertical_weight
.
Estilo Empaquetado
Los elementos de la cadena se empaquetan juntos. Lo que permite que el sesgo afecte a todo el grupo como una unidad.
El valor correspondiente en la cabeza es la constante CHAIN_PACKED
o el valor packed
:
<Button
android:id="@+id/button1"...
app:layout_constraintHorizontal_bias="0.3"
app:layout_constraintHorizontal_chainStyle="packed"/>
El valor del bias se asigna en la cabeza de la cadena. La definición anterior aplica un 30% de sesgo como se mostró en la imagen del inicio.
Márgenes En Cadenas
A diferencia de las márgenes aplicadas en restricciones de posicionamiento relativo, las márgenes en un vínculo bilateral son aditivas. Esto quiere decir, que no se toma la de mayor tamaño, si no que se calcula la suma de sus valores:
En el ejemplo anterior vemos como el botón tiene 16dp de margen en su lado derecho, al igual que el botón 2 en su lado izquierdo.
Cómo ves, el widget es tomado en cuenta con sus márgenes antes de calcular el espacio resultante para la distribución de espacios.
Completar Layout De Calculadora
Para finalizar, completa el layout de la calculadora con el uso de cadenas y el estilo que te parezca conveniente.
En mi caso añadiré un TextView
al inicio para la superficie del resultado y luego usaré MATCH_CONSTRAINT
para los botones en ambas dimensiones.
Como ves, cada botón se expande con un peso equitativo entre las cadenas verticales y horizontales. Además de la existencia de una margen de 16dp con respecto al texto de resultados y el borde inferior del padre.
La definición XML final sería la siguiente:
<?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">
<Button
android:id="@+id/button2"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="2"
android:textSize="30sp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/dot"
app:layout_constraintEnd_toStartOf="@+id/button3"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/button1"
app:layout_constraintTop_toBottomOf="@+id/button5"
tools:visibility="visible" />
<Button
android:id="@+id/button3"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="3"
android:textSize="30sp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/equals"
app:layout_constraintEnd_toStartOf="@+id/subtraction"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/button2"
app:layout_constraintTop_toBottomOf="@+id/button6"
tools:visibility="visible" />
<Button
android:id="@+id/button5"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="5"
android:textSize="30sp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/button2"
app:layout_constraintEnd_toStartOf="@+id/button6"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/button4"
app:layout_constraintTop_toBottomOf="@+id/button8"
tools:visibility="visible" />
<Button
android:id="@+id/button6"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="6"
android:textSize="30sp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/button3"
app:layout_constraintEnd_toStartOf="@+id/multiplication"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/button5"
app:layout_constraintTop_toBottomOf="@+id/button9"
tools:visibility="visible" />
<Button
android:id="@+id/button7"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:text="7"
android:textSize="30sp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/button4"
app:layout_constraintEnd_toStartOf="@+id/button8"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/operations_text"
app:layout_constraintVertical_chainStyle="packed"
tools:visibility="visible" />
<Button
android:id="@+id/button4"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="4"
android:textSize="30sp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/button1"
app:layout_constraintEnd_toStartOf="@+id/button5"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button7"
tools:visibility="visible" />
<Button
android:id="@+id/button1"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="1"
android:textSize="30sp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/button0"
app:layout_constraintEnd_toStartOf="@+id/button2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button4"
tools:visibility="visible" />
<Button
android:id="@+id/button0"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="16dp"
android:text="0"
android:textSize="30sp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/dot"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button1"
tools:visibility="visible" />
<Button
android:id="@+id/button8"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:text="8"
android:textSize="30sp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/button5"
app:layout_constraintEnd_toStartOf="@+id/button9"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/button7"
app:layout_constraintTop_toBottomOf="@+id/operations_text"
app:layout_constraintVertical_chainStyle="packed"
tools:visibility="visible" />
<Button
android:id="@+id/button9"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:text="9"
android:textSize="30sp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/button6"
app:layout_constraintEnd_toStartOf="@+id/division"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/button8"
app:layout_constraintTop_toBottomOf="@+id/operations_text"
app:layout_constraintVertical_chainStyle="packed"
tools:visibility="visible" />
<Button
android:id="@+id/dot"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="16dp"
android:text="."
android:textSize="30sp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/equals"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/button0"
app:layout_constraintTop_toBottomOf="@+id/button2"
tools:visibility="visible" />
<Button
android:id="@+id/equals"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="16dp"
android:text="="
android:textSize="30sp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/addition"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/dot"
app:layout_constraintTop_toBottomOf="@+id/button3"
tools:visibility="visible" />
<Button
android:id="@+id/addition"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="16dp"
android:text="+"
android:textSize="30sp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/equals"
app:layout_constraintTop_toBottomOf="@+id/subtraction"
tools:visibility="visible" />
<Button
android:id="@+id/subtraction"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="−"
android:textSize="30sp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/addition"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/button3"
app:layout_constraintTop_toBottomOf="@+id/multiplication"
tools:visibility="visible" />
<Button
android:id="@+id/multiplication"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="×"
android:textSize="30sp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/subtraction"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/button6"
app:layout_constraintTop_toBottomOf="@+id/division"
tools:visibility="visible" />
<Button
android:id="@+id/division"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:text="÷"
android:textSize="30sp"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/multiplication"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/button9"
app:layout_constraintTop_toBottomOf="@+id/operations_text"
app:layout_constraintVertical_chainStyle="packed"
tools:visibility="visible" />
<TextView
android:id="@+id/operations_text"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#E3F2FD"
android:gravity="bottom|end"
android:padding="16dp"
android:text="0.0"
android:textColor="@color/black"
android:textSize="30sp"
app:layout_constraintDimensionRatio="w,2:3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Objetos De Ayuda Virtual
En este tutorial aprendiste sobre el uso de cadenas en el ConstraintLayout para agrupar elementos con la misión de distribuirlos linealmente. Viste cómo crearlas en el editor de layouts de Android Studio, los diferentes estilos existentes y el computo de sus márgenes.
Puedes continuar hacia el tutorial de objetos de ayuda virtual para estudiar el uso de líneas de guía, barreras y grupos.
Más Contenidos Android
- Restricciones del ConstraintLayout
- Tutoriales de interfaz de usuario en Android (todo)
- Cursos de desarrollo 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!