Column y Row — los layouts básicos
Reemplazan a LinearLayout. Column apila verticalmente, Row horizontalmente:
Column(
modifier = Modifier.fillMaxWidth().padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp), // espacio entre items
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Título")
Text("Subtítulo")
Button(onClick = {}) { Text("Acción") }
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text("Etiqueta", modifier = Modifier.weight(1f)) // weight == layout_weight
Icon(Icons.Default.ArrowForward, contentDescription = null)
}
weight en ComposeModifier.weight(1f) dentro de un Row/Column hace que el componente ocupe el espacio disponible de forma proporcional — exactamente como layout_weight en LinearLayout.
Box — superposición y alineación libre
Reemplaza a FrameLayout. Permite superponer composables y alinearlos dentro del contenedor:
Box(
modifier = Modifier.size(200.dp),
contentAlignment = Alignment.Center
) {
// Imagen de fondo
Image(painter = painterResource(R.drawable.fondo), contentDescription = null,
modifier = Modifier.fillMaxSize())
// Badge sobre la imagen — Alignment.TopEnd la ubica arriba a la derecha
Badge(
modifier = Modifier.align(Alignment.TopEnd).padding(8.dp)
) {
Text("Nuevo")
}
// Texto centrado encima de todo
Text(
text = "Producto",
color = Color.White,
style = MaterialTheme.typography.titleLarge
)
}
Modifiers — el sistema que reemplaza al XML
Los Modifiers describen cómo se decora, posiciona y comporta un composable. Reemplazan todos los atributos XML (android:padding, android:background, android:clickable, etc.):
Text(
text = "Hola",
modifier = Modifier
.fillMaxWidth() // match_parent horizontal
.height(56.dp) // altura fija
.background( // fondo con forma
color = MaterialTheme.colorScheme.surfaceVariant,
shape = RoundedCornerShape(8.dp)
)
.padding(horizontal = 16.dp, vertical = 8.dp) // padding interno
.clickable { /* acción */ }
.semantics { contentDescription = "Saludo" } // accesibilidad
)
Otros modifiers frecuentes:
Modifier.fillMaxSize() // match_parent en ambas dimensiones
Modifier.wrapContentSize() // wrap_content
Modifier.size(48.dp) // ancho y alto iguales
Modifier.width(120.dp)
Modifier.heightIn(min = 48.dp) // altura mínima
Modifier.clip(CircleShape) // recortar con forma
Modifier.border(1.dp, Color.Gray, RoundedCornerShape(4.dp))
Modifier.alpha(0.5f) // transparencia
Modifier.rotate(45f)
Modifier.offset(x = 8.dp, y = 0.dp)
Modifier.padding(16.dp) // padding en todos los lados
Modifier.padding(top = 8.dp, bottom = 16.dp)
Modifier.zIndex(1f) // orden de dibujado
El orden de los Modifiers importa
A diferencia de los atributos XML, el orden en que encadenás los Modifiers cambia el resultado:
// padding ANTES de clickable — el área de click incluye el padding
Modifier
.padding(16.dp)
.clickable { }
// clickable ANTES de padding — el área de click NO incluye el padding
Modifier
.clickable { }
.padding(16.dp)
// background ANTES de padding — el fondo incluye el área de padding
Modifier
.background(Color.Blue)
.padding(16.dp)
// background DESPUÉS de padding — el fondo es solo el contenido
Modifier
.padding(16.dp)
.background(Color.Blue)
Regla prácticaPara botones o items de lista con área de click generosa: clickable primero, luego padding. Para fondos que deben rodear el padding: background primero, luego padding.
LazyColumn y LazyRow — el RecyclerView de Compose
El equivalente de RecyclerView. Solo compone y dibuja los items visibles, reciclando los que salen de pantalla. No necesitás Adapter ni ViewHolder:
@Composable
fun ListaProductos(
productos: List<Producto>,
onProductoClick: (Producto) -> Unit
) {
LazyColumn(
contentPadding = PaddingValues(16.dp), // padding alrededor de toda la lista
verticalArrangement = Arrangement.spacedBy(8.dp) // espacio entre items
) {
// Header
item {
Text(
text = "Productos (${productos.size})",
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(bottom = 8.dp)
)
}
// Lista principal — key para animaciones y rendimiento
items(productos, key = { it.id }) { producto ->
TarjetaProducto(
producto = producto,
onClick = { onProductoClick(producto) }
)
}
// Footer
item {
Spacer(modifier = Modifier.height(80.dp)) // espacio para FAB
}
}
}
Siempre usá keyEl parámetro key en items() permite a Compose identificar cada item de forma estable. Sin él, al agregar o eliminar items, Compose puede reanimar los items equivocados.
LazyVerticalGrid — grillas eficientes
LazyVerticalGrid(
columns = GridCells.Fixed(2), // 2 columnas fijas
// o: columns = GridCells.Adaptive(150.dp) // columnas adaptativas
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(productos, key = { it.id }) { producto ->
TarjetaProductoGrilla(producto = producto)
}
}
Scaffold — la estructura de una pantalla
Scaffold provee la estructura estándar de Material Design con slots para TopBar, BottomBar, FAB y el contenido principal:
@Composable
fun PantallaProductos(
onNavAtras: () -> Unit,
onAgregarClick: () -> Unit
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Productos") },
navigationIcon = {
IconButton(onClick = onNavAtras) {
Icon(Icons.Default.ArrowBack, contentDescription = "Volver")
}
},
actions = {
IconButton(onClick = { /* filtrar */ }) {
Icon(Icons.Default.FilterList, contentDescription = "Filtrar")
}
}
)
},
floatingActionButton = {
FloatingActionButton(onClick = onAgregarClick) {
Icon(Icons.Default.Add, contentDescription = "Agregar")
}
}
) { paddingValues ->
// paddingValues compensa TopBar y BottomBar automáticamente
ListaProductos(
modifier = Modifier.padding(paddingValues),
productos = productos,
onProductoClick = {}
)
}
}