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 = {}
        )
    }
}