Modifiers: Personaliza cualquier vista en Jetpack Compose
Antonio Leiva

Muchas veces no nos es suficiente con la configuración básica que nos provee una vista, y por tanto vamos a necesitar modificarla para adaptarla a nuestras necesidades. Esto es exactamente para lo que sirven los Modifiers.

Es un cajón de sastre que nos da la opción de añadir apariencia o capacidades extra a los Composables.

En el artículo anterior sobre layouts básicos ya vimos cómo usar algunas propiedades del Modifier, pero en este artículo vamos a ver más a fondo qué tipos de modificadores tenemos, cómo se usan, por qué hay modificadores que aplican a unas vistas y otras no, y cómo preparar nuestros Composables para que acepten un Modifier.

Por compararlo con algo, sería el equivalente a las propiedades que usábamos en los XMLs para configurar las vistas.

¡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.

El objeto Modifier

La forma de representar este elemento a nivel de código es usando simplemente un object. El objeto Modifier en realidad es un companion object de una interfaz con el mismo nombre.

En su forma más básica, este objeto solo implementa una serie de operaciones para poder combinar operaciones entre sí. A partir de aquí, se crean un montón de funciones de extensión sobre el mismo para ofrecer muchas funcionalidades distintas.

La forma de usarlo es muy sencilla. El objeto Modifier ya nos da los valores por defecto para esa vista. Si no especificamos nada, es el que se usará.

Si queremos aplicar configuraciones extra, llamamos a las funciones que nos provee.

Su API es tipo builder, así que podemos concatenar varias operaciones seguidas hasta obtener el resultado que queremos. Ya vimos este ejemplo en el artículo anterior:

Row(
    modifier = Modifier
        .fillMaxWidth()
        .height(200.dp)
) { ... }

Aplicamos el fillMaxWidth(), y luego una altura de 200dp.

Tipos de modifiers

La cantidad de modificaciones que podemos hacer sobre cada vista es muy grande, y luego además veremos que varía en función de diversos factores.

¿Qué tipos de modificadores nos podemos encontrar?

  • De posicionamiento y tamaño: Indican a la vista cómo se va a posicionar en función del resto de vistas con las que interactúa, así como el espacio que ocupará en pantalla. Algunos ejemplos son fillMaxWidth(), wrapContentHeight(), los propios width()y height, o aspectRatio() entre muchos otros.
  • De funcionalidad: hay algunos modifiers que amplían funcionalidad, como por ejemplo clickable(), toggeable(), o incluso para añadir scroll a los layouts si su contenido ocupa más que el espacio en pantalla, con horizontalScroll(), verticalScroll() y algunas variantes.
  • De apariencia: aquí hay muchos tenemos algunos que nos permiten añadir un espacio alrededor como padding(), otros la escala como scale(), un borde con border(), el fondo como ya vimos con background(), transparencia con alpha()
  • Listeners: también podemos escuchar ciertos eventos relacionados con la vista, como onFocusChanged(), onKeyEvent() o onSizeChanged() entre otros.

Aprendiendo a usar algunos modifiers

Obviamente el número de modifiers es tan alto que no podemos verlos todos en un solo artículo, así que vamos a ver aquí algunos de los más importantes, e iremos viendo algunos extra en los siguientes artículos.

¿Cómo vamos a probarlos? Vamos a convertir un Text en un botón. Aunque el Text merece su propio vídeo y lo veremos más en profundidad, y existe un Button que hace este ejercicio innecesario en un proyecto propio, nos va a ayudar a practicar con muchos modifiers.

Lo primero es crearnos un Texto dentro de un Box, y lo vamos a centrar. Esto ya sabemos cómo hacerlo del artículo anterior:

Box(contentAlignment = Alignment.Center) {
    Text(text = "Hello Button")
}

Lo primero que vamos a hacer es ponerle un fondo. Este también te lo sabes ya:

Text(
    text = "Hello Button",
    modifier = Modifier
        .background(Color.Cyan)
)
Modifier background

Pero ese texto está ahí un poco aplastado, necesitamos darle un poco de aire, para ello, podemos usar un padding.

Se puede definir tanto un padding completo, como horizontal y vertical, así como individualmente para cada uno de los lados.

En este caso, y solo por probarlo, vamos a especificar uno horizontal y otro vertical de forma independiente:

Text(
    text = "Hello Button",
    modifier = Modifier
        .background(Color.Cyan)
        .padding(horizontal = 16.dp, vertical = 8.dp)
)
Modifier padding

¡Esto va cogiendo forma! ¿Te parece si le añadimos un borde? Para los bordes podríamos hacer un vídeo entero, pero vamos a lo simple: con un ancho y un color:

Text(
    text = "Hello Button",
    modifier = Modifier
        .background(Color.Cyan)
        .padding(horizontal = 16.dp, vertical = 8.dp)
        .border(width = 2.dp, color = Color.Blue)
)

¿Pero qué está pasando aquí? 😱

Modifier border

Aquí llega una lección importante sobre los Modifiers:

El orden de los modifiers es MUY importante.

Se aplican de forma secuencial, y nos dan mucha flexibilidad, pero a la vez tenemos que tener cuidado con el orden que los ponemos. Si queremos tener el borde por fuera, tenemos que aplicarlo antes de aplicar el padding:

modifier = Modifier
    .background(Color.Cyan)
    .border(width = 2.dp, color = Color.Blue)
    .padding(horizontal = 16.dp, vertical = 8.dp)
Reorganización de modifiers

Gracias a que los modifiers se aplican de forma consecutiva, podemos hacer cosas tan locas como esta:

modifier = Modifier
    .background(Color.Cyan)
    .border(width = 2.dp, color = Color.Blue)
    .padding(horizontal = 16.dp, vertical = 8.dp)
    .background(Color.LightGray)
    .border(width = 2.dp, color = Color.Red)
    .padding(8.dp)
)
Varios bordes en los modifiers

Es realmente muy potente. Voy a volver atrás y dejarlo solo con el primer borde, porque ahora vamos a aplicarle un evento de click. Es importante ponerlo al principio, para que aplique sobre el resto de modifiers que hemos añadido:

modifier = Modifier
    .clickable { /* TODO */ }
    .background(Color.Cyan)
    .border(width = 2.dp, color = Color.Blue)
    .padding(horizontal = 16.dp, vertical = 8.dp)
)

El efecto de ripple se aplicará automáticamente:

Modifier clickable

Cómo preparar mis Composables para que acepten un modifier

En artículo anterior te comentaba que al Composable Greeting() le había añadido un modifier para poder ajustar sus modificadores desde fuera y personalizarlo según se necesitara.

Es una práctica recomendada que, si queremos crear vistas genéricas, permitamos aplicarle modifiers. El proceso es realmente muy sencillo.

Tan solo tenemos que pasarle como argumente un modifier a la función, y asignarle el objeto Modifier por defecto. De esta forma, si no queremos cambiar ningún ajuste, se aplicarán los valores por defecto:

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    ...
}cc

Luego ya solo queda asignarle ese modifier al Composable principal de la función:

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    Text(
        modifier = modifier,
        text = "Hello $name!"
    )
}

Los modifiers se adaptan al Scope en el que se usan

Esto es algo muy loco que se consigue gracias a la buen aprovechamiento que han hecho en Compose de la potencia de Kotlin.

Por ejemplo, si volviéramos al Column del artículo anterior, veríamos que podemos hacer Alignment.CenterHorizontally, pero no Alignment.CenterVertically. Sin embargo, con Rows pasa justo al revés.

La magia se esconde en un pequeño detalle:

Imagen

¿Ves ese ColumnScope? Cada layout provee un scope propio que le aporta funciones de extensión extra al objeto Modifier.

Por ejemplo, ColumnScope añade un align(), que usa un Alignment.Horizontal, por lo que solo puede usar CenterHorizontally:

Imagen

¿Y cómo se genera ese scope?

Es relativamente sencillo, gracias al receiver que tiene la lambda que representa el contenido de ese layout.

Imagen

De esa forma no hay que aprenderse de memoria qué modifiers funcionan con cada layout, el compilador y el autocompletado te lo dicen 😱

Este scope no sirve solo para los modifiers, sino que algunos scopes proveen sus propias funciones extra para hacer otras cosas.

Por ejemplo, para las LazyColumns (el equivalente a RecyclerView, que lo veremos en un próximo artículo), tenemos unas funciones que nos permiten añadir los items o los sticky headers:

Imagen

Si te fijas, hasta los items de la lista tienen sus propio LazyItemScope, con sus modifiers extra:

Imagen

Aplicando lo aprendido a nuestro ejemplo

En este paso no vamos a poder avanzar mucho. Teníamos esto:

Vamos a apañar un poco el título de la celda, centrándolo, añadiendo un padding y un color de fondo. Para ello lo rodearemos con un Box:

Box(
    contentAlignment = Alignment.Center,
    modifier = Modifier
        .fillMaxWidth()
        .background(MaterialTheme.colors.secondary)
        .padding(16.dp)
) {
    Text("Title 1")
}

La forma de acceder a las propiedades del tema es usando el objeto MaterialTheme. Aquí le estamos poniendo el color secondary a la celda.

El texto aún se ve un poco pequeño, pero para hablar de textos y tipografías nos vamos a esperar un poco, ya que lo veremos en el siguiente artículo.

Mientras tanto, puedes ver el código haciendo checkout en su commit correspondiente.

Recuerda que puedes acceder a un curso gratuito de Jetpack Compose aquí, donde además tendrás un canal de soporte y muchas sopresas extra. ¡Te veo dentro!

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…

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

5 consejos para estructurar el código en Jetpack Compose

5 consejos para estructurar el código en Jetpack Compose

Escribir la interfaz con código es genial, pero puede ser que pronto se nos vaya de las manos. Aunque la forma de escribir el código de Jetpack Compose es muy natural y relativamente directa, sí que es verdad que en pro de la flexibilidad, también hay algunos...

0 comentarios

Enviar un comentario

Los datos personales que proporciones a través de este formulario quedarán registrados en un fichero de Antonio Leiva Gordillo, 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 *