Modifiers: Personaliza cualquier vista en Jetpack Compose

Antonio Leiva
Antonio Leiva
8 min lectura

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.

https://youtu.be/jej6DiUAlvk

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")
}

Preview TextButton con el texto Hello Button centrado en un Box, sin fondo ni padding

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)
)

Texto Hello Button centrado con fondo cian ajustado al texto, sin padding

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)
)

Hello Button con fondo cian y padding horizontal de 16dp y vertical de 8dp

¡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í? ?

Borde azul pegado al texto Hello Button, dentro del fondo cian, por aplicarse después del padding

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)

Hello Button con fondo cian y borde azul por fuera, al aplicar el border antes del padding

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)
)

Hello Button con fondo gris y borde rojo dentro de un recuadro cian con borde azul

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:

¿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:

¿Y cómo se genera ese scope?

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

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:

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

Aplicando lo aprendido a nuestro ejemplo

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

Preview MediaItem con un Box rojo de 200dp de alto y debajo el texto Title 1 sin estilo

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.

Y si quieres aprender Jetpack Compose a fondo, echa un vistazo a Compose Expert.

Laboratorio

Lo último que hemos probado con agentes

Probamos herramientas, modelos y flujos en proyectos reales, y contamos qué funciona y qué no. Sin humo.

Ir al blog
zsh — devexpert

$git log --oneline blog/

$ git show 7bb9632

commit 7bb9632

Autor: Antonio Leiva · 2026-10-19 · 8 min

Claude Code ya lee AGENTS.md, pero solo como plan B. Prueba controlada con una palabra clave por fichero: qué se carga al empezar, qué pasa con las subcarpetas, el global y los cuatro modos del plugin.

$ ▊