Layouts En Jetpack Compose

En este tutorial aprenderás sobre el uso de layouts en Jetpack Compose, con el objetivo de organizar tus elementos de UI en jerarquías. Verás que esto es posible al llamar funciones componibles dentro de otras funciones componibles.

Al igual que con el sistema de views, Compose nos provee funciones prefabricadas para arreglar nuestros elementos visuales por reglas específicas. Funciones como:

  • Row
  • Column
  • Box
  • Spacer

Dichas funciones usan internamente a la función Layout(), la cual recibe una función composable y organiza su contenido según su lógica de medidas.

@Composable inline fun Layout(
    content: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) 

Lectura: Si aún no has leído Agregar Jetpack Compose A Proyecto Android Studio, te recomiendo ir a revisarlo para que comprendas conceptos prerrequisitos antes de abordar este tutorial.


Ejemplo De Layouts En Jetpack Compose

Tomaremos como ejemplo un diseño simple donde mostraremos tres textos con fondos coloreados, a fin de saber como influye el arreglo, alineación y tamaño proporcionado por las restricciones de cada layout.

Layouts en Jetpack Compose

La idea es probar los layouts y el uso de los modificadores disponibles para decorar la UI. Descargar el código desde el siguiente enlace (Módulo p2_layouts_standard):

Para añadir colores fijos y permitir crear las formas cuadradas de los textos usaremos este archivo de utilidades llamado Childs.kt:

private val aColor = Color(0xFFfff59d)
private val bColor = Color(0xFFffe082)
private val cColor = Color(0xFFffcc80)

fun aModifier(size: Int) = applyModifier(size, color = aColor)
fun bModifier(size: Int) = applyModifier(size, color = bColor)
fun cModifier(size: Int) = applyModifier(size, color = cColor)

private fun applyModifier(size: Int, color: Color): Modifier {
    require(size < 200)
    return Modifier
        .size(size.dp)
        .background(color)
}

Usaremos las funciones de nivel superior aModifier(), bModifier() y cModifier() para aplicar los modificadores base size() y background().


Acomodar Elementos En Columnas (Column)

Usa la función Column para colocar a sus hijos en una secuencia vertical como se muestra en el siguiente ejemplo:

@Composable
@Preview
fun ColumnsExample1() {
    Column(Modifier.size(200.dp)) {
        Text("A", aModifier(50))
        Text("B", bModifier(50))
        Text("C", cModifier(50))
    }
}

El compilador de Compose ubica por defecto al primer elemento en la esquina superior izquierda y prosigue con el siguiente bloque por debajo.

Ejemplo de Column en Compose

Definición De Column

Si revisas la documentación de la función componible Column encontrarás la siguiente firma:

@Composable
inline fun Column(
    modifier: Modifier = Modifier,
    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
    content: ColumnScope.() -> Unit
): @Composable Unit

Donde:

  • modifier: Es el modificador aplicado a la columna
  • verticalArrangement: Define el posicionamiento vertical de los hijos en la columna
  • horizontalAlignment: Define el posicionamiento horizontal de los hijos
  • content: Bloque de contenido donde se declaran los hijos. Expone un recibidor de tipo ColumnScope para proveer modificadores que vinculan los hijos a la columna

Alinear Elementos En La Columna

Si quieres modificar la forma en que se arreglan los hijos, asigna valores del tipo Arrangement.Vertical a verticalArrangement y Alignment.Horizontal para horizontalAligment.

Por ejemplo, apliquemos centrado general tanto vertical como horizontal:

@Composable
@Preview
fun ColumnsExample2() {
    Column(
        Modifier.size(200.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("A", aModifier(50))
        Text("B", bModifier(50))
        Text("C", cModifier(50))
    }
}

El resultado es:

Alineación general desde Column en Compose

Por otro lado, si quieres alineación horizontal individual, usa el modificador ColumnScope.align() para los elementos de la columna:

@Composable
@Preview
fun ColumnsExample3() {
    Column(
        Modifier.size(200.dp)
    ) {
        Text("A", modifier = aModifier(50))
        Text("B", modifier = bModifier(50).align(Alignment.CenterHorizontally))
        Text("C", modifier = cModifier(50).align(Alignment.End))
    }
}

El código anterior alinea el primer elemento a la parte superior izquierda como de costumbre. El segundo va hacia el centro (CenterHorizontally) y el tercero hacia al final (End). La previa del resultado sería:

Distribuir Pesos

Usa el modificador ColumnScope.weight para establecer la preponderancia del tamaño que tendrán de los hijos en la columna:

@Composable
@Preview
fun ColumnsExample4() {
    Column(
        Modifier.size(200.dp)
    ) {
        Text("A", modifier = aModifier(50).weight(1.0f))
        Text("B", modifier = bModifier(50).weight(2.0f))
        Text("C", modifier = cModifier(50).weight(3.0f))
    }
}

En el código anterior, A toma el 1/6 del espacio, B tendrá 1/3 y C se apodera de 1/2:


Acomodar Elementos En Filas (Row)

Usa la función Row para ubicar a sus hijos horizontalmente en la pantalla. Por ejemplo:

@Composable
@Preview
fun RowsExample1() {
    Row(Modifier.size(200.dp)) {
        Text("A", modifier = aModifier(50))
        Text("B", modifier = bModifier(50))
        Text("C", modifier = cModifier(50))
    }
}

El anterior ejemplo toma el ejemplo de los bloques de colores y los distribuye en forma de fila.

Definición De Row

La función Row usa la misma mecánica de Column, pero el arreglo de sus hijos cambia al eje horizontal:

@Composable
inline fun Row(
    modifier: Modifier = Modifier,
    horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
    verticalAlignment: Alignment.Vertical = Alignment.Top,
    content: RowScope.() -> Unit
): @Composable Unit

Y como ves, ahora el último parámetro usa como tipo recibidor a RowScope, el cual tiene modificadores similares a ColumScope.

Distribuir Pesos Horizontalmente

Es el mismo principio de las columnas. Usa el modificador RowScope.weight() y asigna valores que determinen la contribución al peso total que es usado para determinar el tamaño horizontal de cada hijo:

@Composable
@Preview
fun RowsExample2() {
    Row(Modifier.size(200.dp)) {
        Text("A", modifier = aModifier(50).weight(2.0f))
        Text("B", modifier = bModifier(50).weight(5.0f))
        Text("C", modifier = cModifier(50).weight(8.0f))
    }
}

Al distribuir tres bloques con pesos 2, 5 y 8 tendremos:

Ejemplo de RowScope.weight() en Compose

Alinear Elementos De Fila

Cambia el alineamiento y arreglo general de ambos ejes con verticalAlignment y horizontalArrangement:

@Composable
@Preview
fun RowsExample3() {
    Row(
        Modifier.size(200.dp),
        verticalAlignment = Alignment.Bottom,
        horizontalArrangement = Arrangement.Center
    ) {
        Text("A", modifier = aModifier(50))
        Text("B", modifier = bModifier(50))
        Text("C", modifier = cModifier(50))
    }
}

El ejemplo anterior modifica la gravedad de la fila para ubicarla en la parte inferior y alineada al centro horizontal:

Ejemplo de arreglo vertical y alineamiento horizontal de fila en Compose

Aplica el modificador RowScope.align() con el sector de alineación vertical en el que deseas ubicar el contenido del hijo de la fila:

@Composable
@Preview
fun RowsExample4() {
    Row(Modifier.size(200.dp)) {
        Text("A", modifier = aModifier(50))
        Text("B", modifier = aModifier(50).align(Alignment.CenterVertically))
        Text("C", modifier = bModifier(50).align(Alignment.Bottom))
    }
}

El código anterior muestra como enviar a la parte superior, centro y parte inferior a los bloques de la fila:

Ejemplo de RowScope.align() en Compose

Acomodar Elementos En Cajas (Box)

Invoca la función Box() para superponer elementos sobre otros. Las cajas son la contraparte del FrameLayout en Jetpack Compose:

@Composable
@Preview
fun BoxesExample1() {
    Box(Modifier.size(200.dp)) {
        Text("A", modifier = aModifier(150))
        Text("B", modifier = bModifier(100))
        Text("C", modifier = cModifier(50))
    }
}

El ejemplo anterior ubica el bloque C sobre el B y el B sobre el A, mostrando que la caja esta sujeta a cambiar su contenido con las restricciones que vayan llegando.

Ejemplo de Box en Compose

Alinear Elementos En Una Caja

Asigna el valor de alineación para todos los elementos con el parámetro contentAlignment de Box. Puedes usar varios valores que combinan las secciones de ambos ejes rectangulares.

@Composable
@Preview
fun BoxesExample2() {
    Box(Modifier.size(200.dp), contentAlignment = Alignment.BottomCenter) {
        Text("C", modifier = cModifier(150))
        Text("B", modifier = bModifier(100))
        Text("A", modifier = aModifier(50))
    }
}

En el anterior ejemplo enviamos a todos los hijos de la caja hacia la parte inferior del centro vertical (BottomCenter):

Si deseas alinear un elemento individualmente, entonces usa el modificador BoxScope.align():

@Composable
@Preview
fun BoxesExample3() {
    Box(Modifier.size(200.dp)) {
        Text("A", modifier = aModifier(50).align(Alignment.TopCenter))
        Text("B", modifier = bModifier(50).align(Alignment.CenterStart))
        Text("C", modifier = cModifier(50).align(Alignment.BottomEnd))
    }
}

El código anterior alinea a A con la parte superior central. B al extremo izquierdo centrado y C al extremo inferior derecho.


Usar Espaciadores (Spacer)

Usa la función Spacer para representar un layout cuyo espacio estará vacío. Debido a su simplicidad, su definición sólo toma como argumento al modificador para establecer sus propiedades:

@Composable
fun Spacer(modifier: Modifier): @Composable Unit

Por ejemplo, si deseas añadir espacios en blanco entre cada texto de una columna tendrías:

@Composable
@Preview
fun SpacersExample() {

    Column(Modifier.size(200.dp)) {
        Text("A", modifier = aModifier(50))
        Spacer(Modifier.size(100.dp, 10.dp))
        Text("B", modifier = bModifier(50))
        Spacer(Modifier.size(40.dp))
        Text("C", modifier = cModifier(50))
    }
}

El código anterior proyectará un distanciamiento entre A y B por un rectángulo de 100x10dp. Y un espaciado de 40x40dp entre B y C:

Ú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!