Restricciones y orden de los modificadores

En Compose, puedes encadenar varios modificadores para cambiar el aspecto de un elemento componible. Estas cadenas de modificadores pueden afectar las restricciones que se pasan a los elementos componibles, que definen los límites de ancho y altura.

En esta página, se describe cómo los modificadores encadenados afectan las restricciones y, a su vez, la medición y la ubicación de los elementos componibles.

Modificadores en el árbol de la IU

Para comprender cómo se influyen entre sí los modificadores, es útil visualizar cómo aparecen en el árbol de la IU, que se genera durante la fase de composición. Para obtener más información, consulta la sección Composición.

En el árbol de la IU, puedes visualizar los modificadores como nodos de wrapper para los nodos de diseño:

Código para funciones de componibilidad y modificadores, y su representación visual como un árbol de IU.
Figura 1: Modificadores que unen nodos de diseño en el árbol de IU

Si agregas más de un modificador a una función de componibilidad, se crea una cadena de modificadores. Cuando encadenas varios modificadores, cada nodo modificador unifica el resto de la cadena y el nodo de diseño dentro de ella. Por ejemplo, cuando encadenas un modificador clip y un modificador size, el nodo modificador clip une el nodo modificador size, que, a su vez, une el nodo de diseño Image.

En la fase de diseño, el algoritmo que recorre el árbol sigue siendo el mismo, pero también se visita cada nodo modificador. De esta manera, un modificador puede cambiar los requisitos de tamaño y la ubicación del modificador o del nodo de diseño que une.

Como se muestra en la Figura 2, la implementación de los elementos componibles Image y Text consiste en una cadena de modificadores que une un solo nodo de diseño.

Las implementaciones de Row y Column son nodos de diseño que describen cómo diseñar sus elementos secundarios.

La estructura de árbol de antes, pero ahora cada nodo es solo un diseño simple, con muchos nodos de unión de modificadores a su alrededor.
Figura 2: La misma estructura de árbol que en la Figura 1, pero con elementos componibles en el árbol de IU visualizados como cadenas de modificadores.

En síntesis:

  • Los modificadores unen un solo modificador o nodo de diseño.
  • Los nodos de diseño pueden organizar varios nodos secundarios.

En las siguientes secciones, se describe cómo usar este modelo mental para razonar sobre el encadenamiento de modificadores y cómo influye en el tamaño de los elementos componibles.

Restricciones en la fase de diseño

La fase de diseño sigue un algoritmo de tres pasos para encontrar el ancho, la altura y las coordenadas x, y de cada nodo de diseño:

  1. Medir elementos secundarios: Un nodo mide sus elementos secundarios, si los hay.
  2. Decide su propio tamaño: En función de esas mediciones, un nodo decide su propio tamaño.
  3. Coloca los elementos secundarios: Cada nodo secundario se coloca en relación con la posición propia de un nodo.

Constraints ayudan a encontrar los tamaños adecuados para los nodos durante los primeros dos pasos del algoritmo. Las restricciones definen los límites mínimos y máximos para el ancho y la altura de un nodo. Cuando el nodo decide su tamaño, el tamaño medido debe estar dentro de este rango de tamaño.

Tipos de restricciones

Una restricción puede ser una de las siguientes:

  • Bounded: El nodo tiene un ancho y una altura máximos y mínimos.
Restricciones limitadas de diferentes tamaños dentro de un contenedor
Figura 3. Restricciones acotadas
  • Sin límites: El nodo no está restringido a ningún tamaño. Los límites de ancho y altura máximos se establecen en infinito.
Restricciones sin límites que tienen el ancho y la altura establecidos en infinito Las restricciones se extienden más allá del contenedor.
Figura 4. Restricciones sin límites.
  • Exact: Se le pide al nodo que siga un requisito de tamaño exacto. Los límites mínimo y máximo se establecen en el mismo valor.
Restricciones exactas que se ajustan a un requisito de tamaño exacto dentro del contenedor
Figura 5: Restricciones exactas.
  • Combinación: El nodo sigue una combinación de los tipos de restricciones anteriores. Por ejemplo, una restricción podría limitar el ancho y, al mismo tiempo, permitir una altura máxima ilimitada, o bien establecer un ancho exacto, pero proporcionar una altura limitada.
Dos contenedores que muestran combinaciones de restricciones limitadas y sin límites, y anchos y alturas exactos.
Figura 6. Combinaciones de restricciones delimitadas y no delimitadas, y anchos y alturas exactos.

En la siguiente sección, se describe cómo se pasan estas restricciones de un elemento superior a uno secundario.

Cómo se pasan las restricciones de un elemento superior a uno secundario

Durante el primer paso del algoritmo que se describe en Restricciones en la fase de diseño, las restricciones se pasan del elemento superior al secundario en el árbol de la IU.

Cuando un nodo superior mide sus elementos secundarios, proporciona estas restricciones a cada uno de ellos para que sepan qué tan grandes o pequeños pueden ser. Luego, cuando decide su propio tamaño, también se adhiere a las restricciones que pasaron sus propios elementos superiores.

En términos generales, el algoritmo funciona de la siguiente manera:

  1. Para decidir el tamaño que realmente quiere ocupar, el nodo raíz del árbol de la IU mide sus elementos secundarios y reenvía las mismas restricciones a su primer elemento secundario.
  2. Si el elemento secundario es un modificador que no afecta la medición, reenvía las restricciones al siguiente modificador. Las restricciones se pasan por la cadena de modificadores tal como están, a menos que se alcance un modificador que afecte la medición. Luego, se cambia el tamaño de las restricciones según corresponda.
  3. Una vez que se alcanza un nodo que no tiene elementos secundarios (lo que se conoce como "nodo hoja"), este decide su tamaño en función de las restricciones que se pasaron y devuelve este tamaño resuelto a su elemento superior.
  4. El elemento superior adapta sus restricciones según las mediciones de este elemento secundario y llama al siguiente elemento secundario con estas restricciones ajustadas.
  5. Una vez que se miden todos los elementos secundarios de un elemento superior, el nodo superior decide su propio tamaño y se lo comunica a su propio elemento superior.
  6. De esta manera, se recorre todo el árbol en profundidad. Con el tiempo, todos los nodos deciden sus tamaños y se completa el paso de medición.

Para ver un ejemplo detallado, consulta el video Restricciones y orden de los modificadores.

Modificadores que afectan las restricciones

En la sección anterior, aprendiste que algunos modificadores pueden afectar el tamaño de la restricción. En las siguientes secciones, se describen los modificadores específicos que afectan las restricciones.

El modificador size

El modificador size declara el tamaño preferido del contenido.

Por ejemplo, el siguiente árbol de IU se debe renderizar en un contenedor de 300dp por 200dp. Las restricciones están limitadas, lo que permite anchos entre 100dp y 300dp, y alturas entre 100dp y 200dp:

Una parte de un árbol de IU con el modificador de tamaño que une un nodo de diseño y la representación del conjunto de restricciones limitadas que establece el modificador de tamaño en un contenedor.
Figura 7: Restricciones limitadas en el árbol de la IU y su representación en un contenedor.

El modificador size adapta las restricciones entrantes para que coincidan con el valor que se le pasa. En este ejemplo, el valor es 150dp:

Es igual que la figura 7, excepto que el modificador de tamaño adapta las restricciones entrantes para que coincidan con el valor que se le pasa.
Figura 8: El modificador size que ajusta las restricciones a 150dp.

Si el ancho y la altura son menores que el límite de restricción más pequeño o mayores que el límite de restricción más grande, el modificador coincide con las restricciones pasadas lo más cerca posible, sin dejar de cumplir con las restricciones pasadas:

Dos árboles de IU y sus representaciones correspondientes en contenedores. En el primero, el modificador de tamaño acepta las restricciones entrantes; en el segundo, el modificador de tamaño se adapta a las restricciones demasiado grandes lo más posible, lo que genera restricciones que llenan el contenedor.
Figura 9: El modificador size se adhiere a la restricción pasada lo más cerca posible.

Ten en cuenta que encadenar varios modificadores size no funciona. El primer modificador size establece las restricciones mínima y máxima en un valor fijo. Incluso si el segundo modificador de tamaño solicita un tamaño más pequeño o más grande, debe cumplir con los límites exactos que se pasaron, por lo que no anulará esos valores:

Una cadena de dos modificadores de tamaño en el árbol de la IU y su representación en un contenedor, que es el resultado del primer valor que se pasa y no del segundo.
Figura 10: Una cadena de dos modificadores size, en la que el segundo valor que se pasa (50dp) no anula el primer valor (100dp).

El modificador requiredSize

Usa el modificador requiredSize en lugar de size si necesitas que tu nodo anule las restricciones entrantes. El modificador requiredSize reemplaza las restricciones entrantes y pasa el tamaño que especificas como límites exactos.

Si el tamaño que pasas está fuera de las restricciones entrantes, el nodo informa un tamaño que se ajusta a ellas cuando el tamaño se vuelve a pasar por el árbol, y su contenido se centra en ese espacio en cada eje donde los dos tamaños difieren. El contenido más grande que las restricciones se extiende más allá de ellas. En la Figura 11, requiredSize(50) es más pequeño que el 100dp que pasa size, por lo que el contenido de 50dp se centra en un espacio de 100dp:

Los modificadores size y requiredSize encadenados en un árbol de IU y la representación correspondiente en un contenedor. Las restricciones del modificador requiredSize anulan las restricciones del modificador size.
Figura 11. El modificador requiredSize anula las restricciones entrantes del modificador size.

Los modificadores width y height

El modificador size adapta el ancho y la altura de las restricciones. Con el modificador width, puedes establecer un ancho fijo, pero dejar la altura sin definir. Del mismo modo, con el modificador height, puedes establecer una altura fija, pero dejar el ancho sin decidir:

Dos árboles de IU, uno con el modificador de ancho y su representación de contenedor, y el otro con el modificador de altura y su representación.
Figura 12. El modificador width y el modificador height establecen un ancho y una altura fijos, respectivamente.

Para establecer un ancho o una altura que anule las restricciones entrantes, usa requiredWidth o requiredHeight. Al igual que requiredSize, cada uno reemplaza las restricciones entrantes, pero solo en su propio eje: requiredWidth mantiene las restricciones de altura entrantes y requiredHeight mantiene las restricciones de ancho entrantes.

El modificador sizeIn

El modificador sizeIn te permite establecer restricciones mínimas y máximas para el ancho y la altura, dentro de las restricciones entrantes: donde las restricciones entrantes son más estrictas, ganan. Usa el modificador sizeIn si necesitas un control detallado de las restricciones. Los modificadores widthIn y heightIn hacen lo mismo en un eje y dejan sin cambios las restricciones del otro eje. Cada uno acepta un mínimo, un máximo o ambos.

Un árbol de IU con el modificador sizeIn con los anchos y las alturas mínimos y máximos establecidos, y su representación dentro de un contenedor.
Figura 13. El modificador sizeIn con minWidth, maxWidth, minHeight y maxHeight establecidos.

Para establecer un rango que anule las restricciones entrantes, usa requiredSizeIn, o bien requiredWidthIn y requiredHeightIn para un eje. Se relacionan con sizeIn, widthIn y heightIn de la misma manera en que requiredSize se relaciona con size.

Ejemplos

En esta sección, se muestra y explica el resultado de varios fragmentos de código con modificadores encadenados.

Image(
    painterResource(R.drawable.hero),
    contentDescription = null,
    Modifier
        .fillMaxSize()
        .size(50.dp)
)

Este fragmento produce el siguiente resultado:

Un cuadrado azul que llena su contenedor superior.
Figura 14: Image llena el tamaño máximo como resultado de la cadena de modificadores.
  • El modificador fillMaxSize cambia las restricciones para establecer el ancho y la altura mínimos en el valor máximo: 300dp de ancho y 200dp de altura.
  • Aunque el modificador size quiera usar un tamaño de 50dp, debe cumplir con las restricciones mínimas entrantes. Por lo tanto, el modificador size también generará los límites de restricción exactos de 300 por 200, lo que ignorará de manera efectiva el valor proporcionado en el modificador size.
  • El Image sigue estos límites y, luego, informa un tamaño de 300 por 200, que se pasa por todo el árbol.

Image(
    painterResource(R.drawable.hero),
    contentDescription = null,
    Modifier
        .fillMaxSize()
        .wrapContentSize()
        .size(50.dp)
)

Este fragmento produce el siguiente resultado:

Un pequeño cuadrado azul centrado dentro de su contenedor superior.
Figura 15. El Image se centra y se ajusta al tamaño de 50dp.
  • El modificador fillMaxSize adapta las restricciones para establecer el ancho y la altura mínimos en el valor máximo: 300dp de ancho y 200dp de altura.
  • El modificador wrapContentSize restablece las restricciones mínimas. Por lo tanto, mientras que fillMaxSize generó restricciones fijas, wrapContentSize las restablece a restricciones limitadas. El siguiente nodo ahora puede ocupar todo el espacio nuevamente o ser más pequeño que todo el espacio.
  • El modificador size establece las restricciones en los límites mínimo y máximo de 50.
  • El Image se resuelve en un tamaño de 50 por 50, y el modificador size lo reenvía.
  • El modificador wrapContentSize tiene una propiedad especial. Toma su elemento secundario y lo coloca en el centro de los límites mínimos disponibles que se le pasaron. Por lo tanto, el tamaño que comunica a sus elementos superiores es igual a los límites mínimos que se le pasaron.

Si combinas solo tres modificadores, puedes definir un tamaño para el elemento componible y centrarlo en su elemento superior.

Image(
    painterResource(R.drawable.hero),
    contentDescription = null,
    Modifier
        .clip(CircleShape)
        .padding(10.dp)
        .size(100.dp)
)

Este fragmento produce el siguiente resultado:

Una forma circular que se recorta de manera incorrecta debido al orden de los modificadores
Figura 16. Una forma recortada de manera incorrecta debido al orden de los modificadores
  • El modificador clip no cambia las restricciones.
  • El modificador padding reduce las restricciones máximas.
  • El modificador size establece todas las restricciones en 100dp.
  • El Image se adhiere a esas restricciones y, luego, informa un tamaño de 100dp por 100dp.
  • El modificador padding agrega 10dp en todos los lados al tamaño que informa Image, por lo que el diseño con padding informa un ancho y una altura de 120dp.
  • Ahora, en la fase de dibujo, el modificador clip actúa en un lienzo de 120dp por 120dp. Se crea una máscara circular de ese tamaño.
  • Luego, el modificador padding inserta su contenido en 10dp en todos los lados, lo que reduce el tamaño del lienzo para Image a 100dp por 100dp.
  • El Image se dibuja en ese lienzo más pequeño. La imagen se recorta según el círculo original de 120dp, por lo que el resultado no es redondo.

En los siguientes tres fragmentos, se coloca un elemento Box con un color de fondo en el mismo contenedor de 300dp por 200dp.

Box(
    modifier = Modifier
        .requiredWidth(400.dp)
        .height(100.dp)
        .background(Color(0xFF3DDC84))
)

Este fragmento produce el siguiente resultado:

Un rectángulo verde ancho centrado en un contenedor azul claro, que se extiende
    horizontalmente más allá de ambos lados del contenedor.
Figura 17. Con requiredWidth(400.dp), el Box extiende 50dp más allá de cada lado del contenedor 300dp.
  • El modificador requiredWidth reemplaza las restricciones de ancho entrantes por límites exactos de 400dp. Mantiene las restricciones de altura entrantes, de 0 a 200dp.
  • El modificador height establece la altura en 100dp, lo que se ajusta a las restricciones de altura.
  • El Box mide 400dp por 100dp. Ese ancho es mayor que el 300dp que permite el contenedor, por lo que requiredWidth informa un ancho de 300dp a su elemento superior y centra su contenido en ese espacio: el Box extiende 50dp más allá de cada lado del contenedor.

Box(
    modifier = Modifier
        .widthIn(min = 100.dp, max = 400.dp)
        .heightIn(max = 100.dp)
        .fillMaxSize()
        .background(Color(0xFF3DDC84))
)

Este fragmento produce el siguiente resultado:

Un rectángulo verde centrado verticalmente dentro de un contenedor azul claro,
    que coincide con el ancho completo del contenedor sin extenderse más allá de sus lados.
Figura 18. Con widthIn, el Box mide 300dp por 100dp y permanece dentro del contenedor.
  • El modificador widthIn solicita un ancho entre 100dp y 400dp. El ancho máximo entrante es 300dp, por lo que las restricciones de ancho se convierten en 100dp a 300dp.
  • El modificador heightIn establece una altura máxima de 100dp, por lo que las restricciones de altura se convierten en 0 a 100dp.
  • El modificador fillMaxSize establece el ancho y la altura mínimos en esos máximos, por lo que el Box es de 300dp por 100dp y permanece dentro del contenedor.

Box(
    modifier = Modifier
        .requiredSizeIn(minWidth = 100.dp, maxWidth = 400.dp, maxHeight = 100.dp)
        .fillMaxSize()
        .background(Color(0xFF3DDC84))
)

Este fragmento produce el siguiente resultado:

Un rectángulo verde ancho centrado en un contenedor azul claro, que se extiende
    horizontalmente más allá de ambos lados del contenedor.
Figura 19: Con requiredSizeIn, Box mide 400dp por 100dp y extiende 50dp más allá de cada lado del contenedor, el mismo resultado que en la Figura 17.
  • El modificador requiredSizeIn reemplaza las restricciones entrantes por un ancho de 100dp a 400dp y una altura de 0 a 100dp. No se estableció la altura mínima, por lo que se mantiene la altura mínima entrante de 0.
  • El modificador fillMaxSize establece el ancho y la altura mínimos en esos máximos, por lo que el Box mide 400dp por 100dp.
  • Al igual que en el ejemplo de requiredWidth, el ancho es mayor que el que permite el contenedor, por lo que la Box se centra y se extiende 50dp más allá de cada lado.

Con el mismo rango, widthIn y heightIn mantienen el Box dentro del contenedor, y requiredSizeIn no lo hace.