Layouts en Jetpack Compose: Estructura la UI con Box, Column y Row
Antonio Leiva

Organizar los elementos de UI en la pantalla siempre es una parte importante, y tenemos los layouts en Jetpack Compose que nos van a permitir hacerlo de distintas formas en función de nuestras necesidades.

Si te fijas, en el artículo anterior, donde creamos un proyecto Android con Jetpack Compose desde cero, ya tuvimos problemas porque se nos solaparon los dos Texts que utilizamos para el ejemplo, y nos habría gustado por ejemplo poner uno debajo de otro.

Si ya implementabas vistas en Android clásico, tendrás costumbre de utilizar layouts como el FrameLayout, LinearLayout, RelativeLayout , o incluso algunos más modernos como CoordinatorLayout o ConstraintLayout.

En Jetpack Compose, algunos se mantienen (con nombres iguales o distintos) y otros nuevos aparecen, para darnos una experiencia muy cómoda para posicionar elementos en la pantalla.

¡Atención! Si quieres acceder más rápido a todo el contenido en vídeo, organizado, con contenido extra, soporte y muchas más sorpresas, puedes apuntarte gratis a mi formación Compose Expert y ver gratis el primer módulo de más de 3 horas con todo lo necesario para empezar.

Los layouts en Jetpack Compose son más eficientes

Una cosa que nos preocupaba mucho en las vistas clásicas de Android eran los niveles de anidamiento: cuantos más layouts dentro de layouts usáramos, peor iba a ser el rendimiento del repintado.

Esto era porque los layouts tenían que hacer varias pasadas por cada uno de sus hijos para obtener el tamaño final de los mismos. Esto hace que la complejidad pueda ser exponencial según crecen los niveles de anidamiento.

En Jetpack Compose esto se soluciona porque a los layouts solo se les permite visitar a sus hijos una única vez. Por tanto, no nos tenemos que preocupar demasiado de los niveles de anidamiento, ya que la penalización por hacerlo será mínima.

Existen muchos layouts diferentes

Tenemos una gran combinación de layouts por defecto que nos ayudan en las necesidades más habituales del día a día. Por mencionar algunos:

  • Box: El equivalente al FrameLayout. Se pueden organizar distintos elementos alineándolos con la caja que los contiene de una forma muy básica
  • Row y Column: sustituyen al LinearLayout. Organizar elementos de forma consecutiva, o unos al lado de otros o apilados. También se pueden aplicar pesos para definir cuánto ocupará cada elemento
  • LazyRow, LazyColumn y LazyVerticalGrid: los sucesores de RecyclerView, e infinitamente más fáciles de configurar.
  • ConstraintLayout: Sí, sigue existiendo en Jetpack Compose, aunque a día de hoy no hay diseñador que nos ayude. Hay que dominar cómo funciona por código.
  • Scaffold: Un layout que posiciona los elementos típicos de Material. Lo veremos en otro artículo. Recuerda un poco al CoordinatorLayout, aunque funciona muy diferente.
  • Surface: Es algo equivalente al Box, pero que implementa las ideas detrás de las superficies de Material. Gracias a usar Surface, se pueden aplicar mejor la elevación de la superficie, los bordes, es más inteligente en la elección de los colores para el tema…
  • Card: Nuevamente, muy parecido a Surface. Representa una Card de Material, y de hecho si miras su implementación no es más que una Surface con ciertos valores predeterminados modificados.

Además, es relativamente sencillo crear tus propios layouts (sobre todo si los comparamos con los de las vistas clásicas de Android), así que no será extraño que lo hagas para cubrir ciertas necesidades más específicas.

Pero hoy vamos a centrarnos en los básicos: Box, Row y Column.

Nuestro objetivo en los próximos vídeos es conseguir pintar una pantalla como esta:

Layouts en Jetpack Compose: ejemplo de uso

Aquí ya te cuento que vamos a necesitar un Box, un Column y un LazyVerticalGrid, y en los próximos vídeos vamos a ir viendo cómo hacer eso y muchas cosas más.

Ahora vamos a centrarnos en el Box:

Box: El FrameLayout de Jetpack Compose

Es el componente más simple, sí, pero te vas a hartar de usarlo.

Siempre que quieras que dos componentes ocupen el mismo espacio, y que se posicionen de forma relativa a su parent, Box vendrá a tu rescate.

Para ello, lo único que tienes que hacer, es agrupar los Composable hijos dentro de un bloque Box:

Box {
    Greeting("Android")
    Greeting("Antonio")
}

Por defecto, los elementos que incluyamos en un Box se apilan uno encima del otro cuanto más abajo estén en la definición.

En este caso el Greeting("Antonio") estará por encima.

Si te fijas en la preview o ejecutas, verás que no ha cambiado nada. Pero en realidad ahora podemos hacer muchas cosas que antes no. Por ejemplo, haciendo uso de Modifier, podemos indicar al Box cuánto queremos que ocupe. Podemos decirle por ejemplo que ocupe toda la pantalla:

Box(
    modifier = Modifier.fillMaxSize()
) {
    Greeting("Android")
    Greeting("Antonio")
}

No voy a entrar a fondo en qué hace modifier, porque el próximo vídeo va a estar entero dedicado a él, pero nos permite por ejemplo decir qué tamaño queremos que tenga nuestra vista.

Por defecto usa wrapContentSize(), que es el equivalente a poner wrap_content tanto a la altura como a la anchura, pero aquí vamos a usar fillMaxSize(), que sería como usar match_parent en ambas dimensiones.

Ahora podemos colocar las vistas de dentro donde queramos con respecto a su padre. Con la propiedad contentAlignment del propio Box, podemos decidir la posición por defecto:

Uso de Alignment en el Composable Box

Por ejemplo, si usamos Alignment.Center, todos los Composable hijos se centrarán.

Esta es la posición por defecto, pero podemos modificarla individualmente, nuevamente con el argumento modifier. En el próximo artículo te explicaré cómo hacerlo, pero le he añadido a Greeting la opción de aceptar un Modifier, y con esto:

Greeting(
    name = "Android",
    modifier = Modifier.align(Alignment.BottomEnd)
)

Podemos indicarle al Greeting que se posicione por ejemplo en la parte inferior derecha.

BoxWithConstraints

Existe una variante muy especial del Box, que hoy no vamos a ver pero seguramente sí en algún artículo posterior, llamada BoxWithConstraints.

Lo que tiene de especial es que podemos obtener el mínimo y el máximo ancho y alto disponibles, y por tanto mostrar un contenido u otro en función del espacio.

Esta es la forma más habitual de mostrar distintas configuraciones de pantalla en función del tamaño: por ejemplo en teléfonos vs tablets.

Hoy de momento solo te cuento que existe, déjame en los comentarios si quieres que hable de ello.

Column: El Linear Layout vertical de Jetpack Compose

Es muy posible que no nos interese ajustar simplemente los hijos de forma relativa al padre, sino que queramos que se posicionen en relación con sus hermanos.

Para ello, una de las formas más sencillas pero también más habituales es ponerlos unos debajo de otros. Con Column esto se hace de forma muy sencilla:

Column {
    Greeting("Android")
    Greeting("Antonio")
}

Solo con esto, ya conseguimos el objetivo:

Ejemplo sencillo de Column con dos textos

Pero si recuerdas, con LinearLayout podíamos hacer que las vistas ocuparan cierto espacio en función de un peso. Aquí se puede hacer exactamente lo mismo.

Pero antes, quiero enseñarte un par de argumentos extra que tiene la columna, para que veas qué opciones te ofrece.

Para ello, vamos a ponerle a la columna que ocupe todo el ancho, y un alto de 200dp. Esto se consigue usando un literal entero y la propiedad de extensión .dp. Además, le vamos a dar un fondo a cada Greeting, para ver mejor qué pasa:

Column(
    modifier = Modifier
        .fillMaxWidth()
        .height(200.dp)
) {
    Greeting(
        name = "Android",
        modifier = Modifier
            .background(Color.LightGray)
    )
    Greeting(
        name = "Antonio",
        modifier = Modifier
            .background(Color.Yellow)
    )
}

Posicionamiento vertical en Column: verticalArrangement

Por defecto, se posicionan en la esquina superior izquierda:

Column: vertical arrangement

Pero esto se puede modificar. Verticalmente, con la propiedad verticalArrangement, que nos da varias opciones, algunas muy interesantes de mencionar.

  • SpaceEvenly: va a repartir el espacio entre las distintas vistas:
Column: vertical arrangement, spaceEvenly

La forma de usarlo sería:

Column(
    ...
    verticalArrangement = Arrangement.SpaceEvenly,
)
  • SpaceBetween: Muy similar al anterior, pero la primera y última vista no tienen espacio en los bordes. Mejor verlo con un ejemplo con tres vistas:
Column: vertical arrangement, spaceBetween
  • SpaceAround: también muy parecido a SpaceEvenly, pero en vez de dejar el mismo espacio por arriba y por debajo, en los bordes deja la mitad:
Column: vertical arrangement, spaceAround

Posicionamiento horizontal en Column: horizontalAlignment

Aquí las opciones son más sencillas, solo tenemos CenterHorizontally, Start y End. Si usamos la primera:

Column(
    ...
    verticalArrangement = Arrangement.SpaceAround,
    horizontalAlignment = Alignment.CenterHorizontally
) 
Column: horizontal aligment, ejemplo

Pesos en Column: Modifier.weight

Con las opciones anteriores, estamos organizando los elementos mientras ocupan su espacio mínimo. Pero podemos querer que repartan sus espacio en el alto disponible.

Para ello, podemos usar weight:

Column(...) {
    Greeting(
        name = "Android",
        modifier = Modifier
            .weight(2f)
            .background(Color.LightGray)
    )
    Greeting(
        name = "Antonio",
        modifier = Modifier
            .weight(1f)
            .background(Color.Yellow)
    )
}

De esta forma repartimos el espacio en 3. Así 2/3 van para el primer Greeting, y 1/3 para el segundo:

Column: repartir el espacio con pesos (weight)

Los pesos funcionan igual que el LinearLayout, así que tampoco me voy a entretener mucho más aquí.

Row: el LinearLayout horizontal de Jetpack Compose

Aquí tampoco me voy a detener demasiado, ya que son igual que Column. Todo funciona de la misma forma, con la salvedad de que aquí las propiedades se llaman horizontalArrangement y verticalAlignment.

La forma de repartir el espacio es idéntica, solo que a lo ancho en vez de a lo alto.

Por poner un ejemplo muy rápido, si hacemos:

Row(
    modifier = Modifier
        .fillMaxWidth()
        .height(200.dp),
    horizontalArrangement = Arrangement.SpaceAround,
    verticalAlignment = Alignment.CenterVertically
) {
    Greeting(
        name = "Android",
        modifier = Modifier
            .background(Color.LightGray)
    )
    Greeting(
        name = "Antonio",
        modifier = Modifier
            .background(Color.Yellow)
    )
}

Tendremos este resultado:

Ejemplo de Row

Scroll en columnas y filas

¿Qué ocurre si las vistas que contienen los Columns o los Rows ocupan más espacio que la pantalla? Por defecto, no veremos esas vistas y no podremos interactuar con ellas.

Para hacerlo, necesitaríamos un equivalente a ScrollView, y en Compose no existe.

Pero no te preocupes, porque existe otra opción, y en el artículo de Modifiers lo veremos.

Aplicando lo aprendido a nuestro ejemplo

Nuestro primer objetivo es pintar un elemento de los de nuestra App, que tiene la siguiente pinta:

Imagen de lo que queremos conseguir con los layouts en Jetpack Compose

Todavía nos falta por aprender unas cuantas cosas para lograrlo, pero lo que tenemos claro es que necesitamos una Column para poder poner unos elementos encima de otros, y al menos un Box para poder apilar una imagen de fondo y el símbolo de vídeo.

Column {
    Box(
        modifier = Modifier
            .height(200.dp)
            .fillMaxWidth()
            .background(color = Color.Red)
    ) {

    }
    Text("Title 1")
}

De momento, con lo que conocemos, lo vamos a dejar así. Tenemos un Column con un Box y un Text, y el resultado es este:

Puedes ver el código en el repositorio en Github, moviéndote al commit correspondiente.

Aún estamos lejos del resultado esperado, pero prepárate porque se vienen curvas.

En los próximos artículos veremos cómo aplicar paddings, cargar imágenes, sacarle el máximo partido a los textos, y un largo etcétera.

En particular, el siguiente trata sobre los Modifiers en Jetpack Compose, un concepto que es vital entender a fondo para crear tus interfaces.

Apúntate ahora a Compose Expert y accede a más de 3h de contenido gratuito, soporte, extras y mucho más totalmente gratis.

Quizá también te interese…

Temas, colores, tipografías y formas en Jetpack Compose

Temas, colores, tipografías y formas en Jetpack Compose

Si vienes del sistema clásico de vistas, recordarás que toda la definición de temas se hacía de una forma bastante tediosa a través de styles en XML. Si odias tu vida (o estás en una App donde mezclas XMLs y Jetpack Compose), aún puedes seguir usando esos temas...

Usando Cards de Material Design en Jetpack Compose

Usando Cards de Material Design en Jetpack Compose

Tenemos ya una App de lo más resultona después de todos los artículos que hemos visto hasta ahora. Puedes encontrarlos todos bien ordenaditos de forma gratuita en el área privada de Compose Expert. https://youtu.be/iZiXpWRIl3U Pero antes de finalizar nuestro camino,...

Navegación en Jetpack Compose con Navigation Compose

Navegación en Jetpack Compose con Navigation Compose

Jetpack Compose es un cambio de paradigma enorme en muchos aspectos. Cambia la forma de pensar en casi todos los puntos involucrados en el desarrollo de una App Android. Y la navegación no iba a ser menos. ¿Cómo se navega en Jetpack Compose? ¿Qué opciones tenemos?...

0 comentarios

Enviar un comentario

Los datos personales que proporciones a través de este formulario quedarán registrados en un fichero de DevExpert, S.L.U., con el fin de gestionar los comentarios que realizas en este blog. La legitimación se realiza a través del consentimiento de la parte interesada. Si no se acepta, no podrás comentar en este blog. Los datos que proporciona solo se utilizan para evitar el correo no deseado y no se usarán para nada más. Puede ejercer los derechos de acceso, rectificación, cancelación y oposición en contacto@devexperto.com.

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Acepto la política de privacidad *