Saltar al contenido
K
Jetpack ComposePaso a paso40 min de lectura · 8 min de máquina Básico

Compose: composables y modifiers

Al terminar: Escribir tus primeros composables y colocarlos con Row, Column, Box y Modifier, y verlos en la vista previa.

Hasta ahora, la pantalla de Marcador era una columna de botones de prueba. En esta lección aprendes las piezas con las que se dibuja una pantalla de verdad con Jetpack Compose: qué es un composable, cómo se colocan los elementos con Row, Column y Box, y por qué el orden de los Modifier cambia lo que ves. Al terminar tendrás una tarjeta con «Rayo FC 2 - 1 Toros», dos recuadros que enseñan el orden de los modificadores y un escudo con una insignia, todo visto en la vista previa de Android Studio, sin abrir el emulador. La pantalla completa, con su barra superior, llega en Compose: Scaffold, slots y tema.

Importa porque toda la interfaz del resto del módulo se escribe con estas piezas. Temas: paradigma declarativo, composables, orden de los modifiers, Row, Column, Box y las vistas previas.

Declarativo: describes la pantalla, no la modificas

Jetpack Compose (Compose, para abreviar) es la librería de Google con la que se dibuja la pantalla de una app de Android escribiendo funciones de Kotlin. Ya la usas: Text, Column y Button de MainActivity son suyos. Lo que cambia hoy es que aprendes a pensar con ella.

Views es el sistema anterior de Android para pintar pantallas: un fichero XML describe un árbol de objetos (TextView, Button) y luego el código los modifica a mano. Compose funciona al revés.

ejemplo: el mismo texto con Views y con Compose (ilustración)
// Views: el objeto existe y TÚ lo cambias cuando cambia el dato.
textoMarcador.text = "2 - 1"
 
// Compose: describes cómo se ve la pantalla PARA UN DATO, y punto.
@Composable
fun Marcador(local: Int, visitante: Int) {
    Text("$local - $visitante")
}

Con Views tienes que acordarte de actualizar cada trozo de pantalla cuando cambia un dato. Con Compose, cuando el dato cambia, el sistema vuelve a llamar a tu función con el dato nuevo. A esa nueva llamada se le llama recomposición (recomposition); en la próxima lección verás qué la provoca.

Un composable es una función que emite

Un composable es una función marcada con @Composable (una anotación: una etiqueta que el compilador lee). No devuelve la pantalla: emite elementos al árbol de la pantalla (la estructura de elementos anidados que Compose guarda en memoria: una Column que contiene un Text y un Button, por ejemplo), uno por cada composable que llama. Convenciones:

  • Se nombra con mayúscula, como un sustantivo: TarjetaDePartido.
  • Recibe un parámetro modifier: Modifier = Modifier, el primero de los opcionales, para que quien la use pueda colocarla (qué es un Modifier se explica justo debajo).
  • Solo se llama desde otro composable (o desde setContent).

El orden de los modifiers importa

Un Modifier es una lista de decoraciones que se encadenan con puntos: tamaño, margen, color de fondo, clic. Cada una envuelve a lo que viene después, de fuera hacia dentro. 16.dp son 16 dp (píxeles independientes de la densidad de la pantalla: miden lo mismo a la vista en un móvil de pantalla nítida que en uno de pantalla basta): dp es una propiedad de extensión sobre Int, como las de Extensiones.

Ahora sí se entiende la convención de arriba. Modifier (mayúscula) es el punto de partida: una lista vacía de decoraciones. modifier (minúscula) es el parámetro que te entrega quien te llama, con sus decoraciones. Encadenas las tuyas sobre él (modifier.fillMaxWidth()) y solo en la raíz (el primer composable de tu función, el que contiene a los demás); los hijos empiezan de cero con Modifier. Verás las dos formas en TarjetaDePartido.

ejemplo mínimo: dos órdenes, dos resultados (con comentarios)
// color: cualquier color; en la pantalla de hoy será uno del tema de Material.
// A) primero el margen (vacío) y después el color: el color solo cubre el texto.
Text("Rayo FC", Modifier.padding(16.dp).background(color))
 
// B) primero el color y después el margen: el color cubre todo, con aire dentro.
Text("Rayo FC", Modifier.background(color).padding(16.dp))

Esquema del resultado (una ilustración, no una captura; · es margen transparente):

A) padding y luego background         B) background y luego padding
···············                       ███████████████
··███████████··                       █             █
··█ Rayo FC █··                       █   Rayo FC   █
··███████████··                       █             █
···············                       ███████████████
el color pega al texto                el color incluye el margen

En la pantalla de este paso verás A y B una debajo de la otra.

Row, Column y Box

El eje principal es la dirección en la que se colocan los hijos (vertical en Column, horizontal en Row); el transversal es el perpendicular. Arrangement reparte el espacio a lo largo del eje principal; Alignment posiciona cada hijo en el transversal.

ComposableColoca los hijosEje principalEje transversal
ColumnEn verticalvertical: verticalArrangementhorizontal: horizontalAlignment
RowEn horizontalhorizontal: horizontalArrangementvertical: verticalAlignment
BoxUnos encima de otros (el último, delante)No tieneLos hijos se colocan con contentAlignment (todos) o Modifier.align(…) (uno)

Arrangement.SpaceBetween reparte el espacio sobrante entre los hijos; Arrangement.spacedBy(24.dp) deja siempre 24 dp entre ellos.

Las tres juntas, en una ficha de jugador inventada (no es la pantalla de Marcador). Antes, los modificadores básicos que usa:

  • fillMaxWidth(): ocupa todo el ancho disponible. fillMaxSize(): todo el ancho y todo el alto.
  • size(48.dp): ancho y alto fijos, de 48 dp.
  • clip(CircleShape): recorta lo que se dibuja con forma de círculo. En un Box da un círculo; en un Text recorta el fondo que le pongas después y lo deja con los extremos redondeados, como una píldora (por eso sirve para una insignia).
  • padding(12.dp): margen igual por los cuatro lados. También existe padding(horizontal = 6.dp) (solo izquierda y derecha), padding(vertical = …) y padding(top = …). Se pueden dar juntos: padding(horizontal = 8.dp, vertical = 2.dp).

Y dos nombres del tema: Material (Material 3) es el sistema de diseño de Google, con sus colores y estilos de letra ya elegidos; MaterialTheme.colorScheme y MaterialTheme.typography los piden. Se explican en la lección siguiente, en «El tema»; por ahora, léelos como «un color» y «un estilo de letra». Dos colores más del tema: colorScheme.error es un rojo de aviso y colorScheme.onError es el color para escribir encima de él y que se lea (el prefijo on significa «sobre»: color = MaterialTheme.colorScheme.onError en un Text le pone esa letra).

ejemplo mínimo: Row, Column y Box (con comentarios)
@Composable                                                     // anotación: esta función emite elementos
fun FichaDeJugador(nombre: String, dorsal: Int, modifier: Modifier = Modifier) {  // modifier, el primero de los opcionales
    Row(                                                        // hijos en horizontal
        modifier.fillMaxWidth().padding(12.dp),                 // la raíz encadena sobre el modifier que recibe
        horizontalArrangement = Arrangement.spacedBy(16.dp),    // eje principal: 16 dp entre hijos
        verticalAlignment = Alignment.CenterVertically,         // eje transversal: centrados en vertical
    ) {
        Box(Modifier.size(48.dp)) {                             // los hijos empiezan de cero con Modifier
            Box(
                Modifier.fillMaxSize().clip(CircleShape)        // ocupa los 48 dp y se recorta en círculo
                    .background(MaterialTheme.colorScheme.secondaryContainer),  // color del tema
                contentAlignment = Alignment.Center,            // todo lo de dentro, al centro
            ) { Text("$dorsal", fontWeight = FontWeight.Bold) } // FontWeight.Bold: letra en negrita
            Text("C", Modifier.align(Alignment.TopEnd))         // «C» de capitana: un solo hijo, arriba a la derecha, delante
        }
        Column {                                                // hijos en vertical
            Text(nombre, style = MaterialTheme.typography.titleMedium)  // un estilo de texto del tema
            Text("Delantera")
        }
    }
}

Las vistas previas: @Preview

Una vista previa (Preview) dibuja un composable en el propio Android Studio sin instalar la app en el emulador. Se escribe como otra función @Composable, sin parámetros, marcada con @Preview y que llama a tu composable con datos de ejemplo. Dibujar con la app tarda segundos; con la vista previa, un parpadeo, y puedes ver varios casos a la vez.

ejemplo mínimo: una vista previa (con comentarios)
@Preview(showBackground = true)                                 // dibújalo en el editor, sobre fondo blanco
@Composable
private fun FichaDeJugadorPreview() {                           // sin parámetros; private: solo para el editor
    MaterialTheme { FichaDeJugador("Ana", dorsal = 9) }         // datos de ejemplo; MaterialTheme da los colores de Material
}

Necesita dos librerías, ui-tooling-preview (la anotación @Preview) y ui-tooling (el motor que la dibuja, solo en debug). Ya vienen en tu proyecto: lo verás abajo.

Cómo abrirla. Abre el fichero que tiene la función con @Preview. En la esquina superior derecha del editor hay tres botones: Code (solo el código), Split (el código a un lado y la vista previa al otro) y Design (solo la vista previa). Pulsa Split. Si la vista previa dice que necesita compilar, pulsa el aviso Build & Refresh (construir y refrescar) y espera unos segundos; cada vez que cambies el código y quieras ver el resultado, vuelve a pulsarlo. Se dibuja sobre fondo blanco porque la anotación lleva showBackground = true.

Si no ves esos tres botones, busca en el borde derecho del editor una pestaña llamada Preview (o View → Tool Windows). Lo que importa es que el IDE dibuje la función @Preview sin ejecutar nada.

Profundizar: la etiqueta de oferta

Un ejemplo ajeno al Marcador, para ver el orden de los modifiers en una etiqueta de una tienda inventada. No hace falta escribirlo: léelo.

ejemplo: la etiqueta de oferta de una tienda (con comentarios)
@Composable
fun EtiquetaDeOferta(texto: String, modifier: Modifier = Modifier) {
    Text(
        texto,
        modifier
            .padding(8.dp)                  // 1) margen de fuera, transparente
            .background(Color.Yellow)       // 2) el fondo amarillo
            .padding(12.dp),                // 3) aire dentro del amarillo
        fontWeight = FontWeight.Bold,       // letra en negrita
    )
}
 
@Preview(showBackground = true)
@Composable
private fun EtiquetaDeOfertaPreview() {
    Column {                                // una debajo de otra
        EtiquetaDeOferta("Pan -20 %")
        EtiquetaDeOferta("Leche 2x1")
    }
}

Color.Yellow es un color fijo (lleva import androidx.compose.ui.graphics.Color). Esto es lo que se ve en la vista previa: dos recuadros amarillos, uno debajo de otro, con su texto en negrita y 12 dp de amarillo alrededor del texto, y 8 dp en blanco por fuera (16 entre los dos recuadros). Sin el primer padding(8.dp), el de fuera, el amarillo llegaría al borde; sin el último, el de dentro, el texto quedaría pegado al amarillo.

Reto · Ejercicio: la ficha de producto

Tu primer ejercicio de Compose se escribe en la carpeta ejercicios de tu proyecto, la que creaste en La rotación y la muerte del proceso: crea ahí una subcarpeta nueva, ficha_de_producto: clic derecho sobre com.sebastiannarvaez.marcador → New → Package → escribe .ejercicios.ficha_de_producto (con el punto delante, detrás de lo que ya trae el campo). Después, clic derecho sobre esa carpeta → New → Kotlin Class/File → File → FichaDeProducto. Dentro de ejercicios irá una subcarpeta por ejercicio. Los ejercicios no se borran: se quedan ahí y la app no los usa.

En ese fichero, crea un composable FichaDeProducto que reciba un nombre, un precio, un texto de oferta y, opcionalmente, un modificador. Dibuja una fila a todo lo ancho, con 12 dp de margen y 16 dp entre sus dos hijos, centrados en vertical:

  • a la izquierda, un escudo de 56 dp: un círculo de color con la primera letra del nombre en negrita y, encima, en la esquina de arriba a la derecha, el texto de la oferta, en el color de escribir sobre rojo, con un fondo rojo de extremos redondeados y 4 dp de margen a los lados dentro del rojo;
  • a la derecha, en columna, el nombre con el estilo mediano del tema y, debajo, el precio en negrita sobre fondo amarillo, con 4 dp de aire por encima y fuera del amarillo, y 8 dp a los lados y 2 dp arriba y abajo dentro del amarillo.

Pista: la primera letra del nombre te la da take(1) sobre el texto, el mismo take que en las listas.

Añade una vista previa con dos fichas, una debajo de otra: Pan, 0,80 €, «-20%»; y Leche, 1,10 €, «2x1». Ábrela en Split. Debes ver dos filas. En cada una, un círculo con «P» o «L» y su insignia roja («-20%», «2x1») arriba a la derecha. Al lado, «Pan» o «Leche» y, debajo, el precio en negrita con un recuadro amarillo pegado al texto por arriba y por abajo, con un poco de aire a los lados. Entre el nombre y el amarillo queda un hueco blanco de 4 dp.

Ver solución
app/src/main/java/com/sebastiannarvaez/marcador/ejercicios/ficha_de_producto/FichaDeProducto.kt
package com.sebastiannarvaez.marcador.ejercicios.ficha_de_producto
 
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
 
@Composable
fun FichaDeProducto(nombre: String, precio: String, oferta: String, modifier: Modifier = Modifier) {
    Row(
        modifier.fillMaxWidth().padding(12.dp),
        horizontalArrangement = Arrangement.spacedBy(16.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Box(Modifier.size(56.dp)) {
            Box(
                Modifier.fillMaxSize().clip(CircleShape).background(MaterialTheme.colorScheme.secondaryContainer),
                contentAlignment = Alignment.Center,
            ) { Text(nombre.take(1), fontWeight = FontWeight.Bold) }
            Text(
                oferta,
                Modifier
                    .align(Alignment.TopEnd)
                    .clip(CircleShape)
                    .background(MaterialTheme.colorScheme.error)
                    .padding(horizontal = 4.dp),
                color = MaterialTheme.colorScheme.onError,
            )
        }
        Column {
            Text(nombre, style = MaterialTheme.typography.titleMedium)
            Text(
                precio,
                Modifier
                    .padding(top = 4.dp)
                    .background(Color.Yellow)
                    .padding(horizontal = 8.dp, vertical = 2.dp),
                fontWeight = FontWeight.Bold,
            )
        }
    }
}
 
@Preview(showBackground = true)
@Composable
private fun FichaDeProductoPreview() {
    MaterialTheme {
        Column {
            FichaDeProducto("Pan", "0,80 €", "-20%")
            FichaDeProducto("Leche", "1,10 €", "2x1")
        }
    }
}

Paso · Instala foundation

Objetivo. Que el catálogo declare foundation, la librería de los bloques básicos de pantalla.

Gradle Scripts
libs.versions.toml (Version Catalog "libs")se modifica: [libraries] y [bundles]
build.gradle.kts (Module :app)no cambia: ya pide el bundle
Los dos ficheros de la instalación. El de `:app` no cambia.

1 · Qué instalas. foundation trae Row, Column, Box, padding, el desplazamiento… Ya llega con material3 (una dependencia transitiva); la declaras tú porque tu código la usa directamente, como las corrutinas en Gradle: ficheros, catálogo y dependencias.

2 · La versión. Nada en [versions]: la fija el BOM de Compose (la lista de versiones que casan entre sí), ya en tu :app.

3 y 4 · La librería y el bundle. En Gradle Scripts → libs.versions.toml (Version Catalog "libs"), añade la línea al final de [libraries] y cambia la de compose en [bundles] (la que creaste en Gradle: build types y BuildConfig):

catálogo: línea nueva y bundle cambiado (con comentarios)
[libraries]
# Sin version.ref: la versión la pone el BOM de Compose
androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" }
 
[bundles]
# Antes: ["androidx-compose-ui", "androidx-compose-material3"]. Alias con guiones y entre comillas
compose = ["androidx-compose-ui", "androidx-compose-foundation", "androidx-compose-material3"]

5 · Plugins y build.gradle.kts. Ninguno cambia: :app ya pide implementation(libs.bundles.compose), y el bundle lleva una librería más.

6 · Sync Now. Debe terminar sin errores rojos, sin cambios visibles (la librería ya llegaba). Fallos típicos: un alias del bundle que no existe en [libraries] (o con puntos) y haber borrado la línea del BOM de :app. Más en la caja «Si Sync sale en rojo» de Los ficheros de Gradle de tu proyecto.

Paso · las piezas de la pantalla

Objetivo. Crear PantallaMarcador.kt con tres piezas: la tarjeta del partido, los dos órdenes de modifiers y el escudo con su insignia, y verlas juntas en una vista previa. La pantalla con barra superior y botón llega en la lección siguiente. Al terminar verás, en la vista previa: una tarjeta con Rayo FC, 2 - 1 y Toros, los dos recuadros A y B, y un círculo «RF» con una insignia roja.

app
kotlin+java → com.sebastiannarvaez.marcador
featurese crea
marcador
ui
PantallaMarcador.ktse crea
ui.themeya existe; sin tocar
Un fichero nuevo, en una carpeta nueva.

1 · Crea el fichero. En el panel del proyecto, con la vista Android, despliega app → kotlin+java. Todavía no hay ninguna carpeta para esta pantalla, así que primero se crea el paquete (ya has creado otros, como ejercicios y laboratorio). Este se llama feature.marcador.ui: cada punto es una carpeta dentro de la anterior, es decir feature → marcador → ui.

  1. Clic derecho sobre com.sebastiannarvaez.marcador → New → Package.
  2. El campo ya trae com.sebastiannarvaez.marcador: escribe .feature.marcador.ui al final (con el punto delante) y confirma. Android Studio puede juntar en una sola línea las carpetas que solo tienen una hija; si ves feature.marcador.ui en una línea, es la misma carpeta.
  3. Clic derecho sobre feature.marcador.ui → New → Kotlin Class/File.
  4. Escribe PantallaMarcador y en la lista elige File (no Class). Se abre un editor cuya primera línea ya es package com.sebastiannarvaez.marcador.feature.marcador.ui.

Al terminar, feature.marcador.ui aparece con PantallaMarcador.kt dentro. No lo confundas con ui.theme, que es de la plantilla y está al lado, en la raíz.

2 · Los imports. Un import es la línea que dice de qué librería viene cada nombre que usas (Text, Card…). Este fichero tiene 24 por ahora (están en el primer bloque de abajo). Android Studio marca en rojo cada nombre sin import; Option+Enter → Import añade la línea correcta (si hay dos opciones, elige la de androidx.compose…).

Piezas nuevas de este paso:

  • Card { … }: un panel con fondo y esquinas redondeadas.
  • .background(color): pinta un fondo.
  • Ejemplo.rayoContraToros y partido.marcador(): el partido de ejemplo de :torneo y la función de extensión (la escribiste en Flow: scan y combine) que devuelve su marcador actual, con local y visitante.
  • FontWeight.Bold (letra en negrita) y MaterialTheme.typography.displaySmall (el estilo de texto grande de Material, que se usa para el resultado; titleMedium es el mediano para los nombres).
  • MaterialTheme.colorScheme.tertiaryContainer, .error y .onError: colores del tema. «Container» es un fondo suave; onError es el color pensado para escribir encima del color error (rojo) y que se lea.

El fichero tiene tres composables y una vista previa. Esto es lo que hace cada uno (los nombres, tal cual, porque las lecciones siguientes los usan):

  • TarjetaDePartido: recibe un partido, un marcador y, opcionalmente, un modificador. Es una tarjeta a todo lo ancho con tres textos en fila: el nombre del local, el resultado en grande y en negrita («2 - 1») y el nombre del visitante.
  • OrdenDeLosModifiers: dos veces el texto «Rayo FC» con un margen de 16 dp y un fondo de color, primero con el margen antes del fondo (A) y después al revés (B), y encima de cada uno una frase que lo explique: «A) padding y luego background: el color NO incluye el margen» y «B) background y luego padding: el color SÍ incluye el margen».
  • InsigniaSobreEscudo: un escudo de 64 dp, un círculo de color con las letras «RF» en negrita en el centro y, encima, en la esquina de arriba a la derecha, un «2» sobre un fondo rojo redondeado.
  • PiezasDelMarcadorPreview: una vista previa provisional que pone los tres composables en una columna, separados 24 dp, con el partido de ejemplo. En la lección siguiente la sustituirás por la de la pantalla entera.

En tu proyecto, ese fichero es app → kotlin+java → com.sebastiannarvaez.marcador → feature → marcador → ui → PantallaMarcador.kt. Un texto lleva tilde («SÍ»): escríbelo tal cual.

Intenta escribirlo tú: PantallaMarcador.kt con los tres composables y la vista previa, a partir de esa descripción. Lo que debes ver en la vista previa: la tarjeta Rayo FC 2 - 1 Toros, el recuadro A pegado al texto y el B con aire alrededor, y el círculo «RF» con el «2» rojo en la esquina. Si te atascas, abre el bloque.

Intenta primero. Cuando termines, compara con la solución.Mostrar el código

La cabecera de PantallaMarcador.kt (el paquete y los import); si prefieres, deja que Option+Enter los vaya añadiendo:

app/src/main/java/com/sebastiannarvaez/marcador/feature/marcador/ui/PantallaMarcador.kt
package com.sebastiannarvaez.marcador.feature.marcador.ui
 
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.sebastiannarvaez.marcador.torneo.Ejemplo
import com.sebastiannarvaez.marcador.torneo.Marcador
import com.sebastiannarvaez.marcador.torneo.Partido
import com.sebastiannarvaez.marcador.torneo.marcador

La tarjeta del partido:

app/src/main/java/com/sebastiannarvaez/marcador/feature/marcador/ui/PantallaMarcador.kt
@Composable
fun TarjetaDePartido(partido: Partido, marcador: Marcador, modifier: Modifier = Modifier) {
    Card(modifier.fillMaxWidth()) {
        Row(
            Modifier.fillMaxWidth().padding(20.dp),
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Text(partido.local.nombre, style = MaterialTheme.typography.titleMedium)
            Text(
                "${marcador.local} - ${marcador.visitante}",
                style = MaterialTheme.typography.displaySmall,
                fontWeight = FontWeight.Bold,
            )
            Text(partido.visitante.nombre, style = MaterialTheme.typography.titleMedium)
        }
    }
}

Las dos versiones del orden de los modifiers:

app/src/main/java/com/sebastiannarvaez/marcador/feature/marcador/ui/PantallaMarcador.kt
@Composable
fun OrdenDeLosModifiers() {
    Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
        Text("A) padding y luego background: el color NO incluye el margen")
        Text(
            "Rayo FC",
            Modifier
                .padding(16.dp)
                .background(MaterialTheme.colorScheme.primaryContainer),
        )
        Text("B) background y luego padding: el color SÍ incluye el margen")
        Text(
            "Rayo FC",
            Modifier
                .background(MaterialTheme.colorScheme.primaryContainer)
                .padding(16.dp),
        )
    }
}

El escudo con la insignia (un Box que superpone):

app/src/main/java/com/sebastiannarvaez/marcador/feature/marcador/ui/PantallaMarcador.kt
@Composable
fun InsigniaSobreEscudo(modifier: Modifier = Modifier) {
    Box(modifier.size(64.dp)) {
        Box(
            Modifier
                .fillMaxSize()
                .clip(CircleShape)
                .background(MaterialTheme.colorScheme.tertiaryContainer),
            contentAlignment = Alignment.Center,
        ) { Text("RF", fontWeight = FontWeight.Bold) }
        Text(
            "2",
            Modifier
                .align(Alignment.TopEnd)
                .clip(CircleShape)
                .background(MaterialTheme.colorScheme.error)
                .padding(horizontal = 6.dp),
            color = MaterialTheme.colorScheme.onError,
        )
    }
}

Y, a continuación, la vista previa provisional que las junta:

PantallaMarcador.kt: la vista previa provisional
@Preview(showBackground = true)
@Composable
private fun PiezasDelMarcadorPreview() {
    MaterialTheme {
        val partido = Ejemplo.rayoContraToros
        Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(24.dp)) {
            TarjetaDePartido(partido, partido.marcador())
            OrdenDeLosModifiers()
            InsigniaSobreEscudo()
        }
    }
}

Todavía nadie llama a estas piezas desde la app: eso viene en la lección siguiente. Mientras escribes, Android Studio puede marcar en rojo Ejemplo, Marcador o marcador si falta su import; pulsa Option+Enter sobre cada uno.

Paso · mira la vista previa

Abre PantallaMarcador.kt en Split, como en el ejercicio (si pide compilar, Build & Refresh). Debes ver, sobre fondo blanco, la tarjeta, los recuadros A y B y el escudo «RF» con su «2» rojo.

Punto de control

  • Sync Now termina sin errores rojos, y Gradle Scripts → libs.versions.toml tiene androidx-compose-foundation en [libraries] y en el bundle compose.
  • En el modo Split, la vista previa de PantallaMarcador.kt muestra la tarjeta Rayo FC 2 - 1 Toros, los recuadros A y B y el círculo «RF» con su «2» rojo.
  • El recuadro A tiene el color pegado al texto y el B lo tiene con aire alrededor.
  • La app todavía no cambia en el emulador: sigue mostrando la pantalla de pruebas de las lecciones de Android. Los tests de :torneo siguen en 137.

Comprueba lo que entendiste

4 preguntas · necesitas 3 para completar la lección

  1. 1.En Views escribías textoMarcador.text = "2 - 1" cuando cambiaba el dato. ¿Qué hace Compose en su lugar?

  2. 2.¿Qué se ve con Text("Rayo FC", Modifier.padding(16.dp).background(color))?

  3. 3.Quieres que un botón de 64 dp muestre una insignia superpuesta en su esquina superior derecha. ¿Qué composable usas para superponer?

  4. 4.¿Qué necesita una función para que Android Studio la dibuje en la vista previa?

Faltan 4 de 4