ListDetailPaneScaffold — el patrón más común
El patrón list-detail (lista a la izquierda, detalle a la derecha) es el más usado en tablets. En teléfono, la lista y el detalle se muestran en pantallas separadas con navegación entre ellas. En tablet, ambas se muestran simultáneamente.
// implementation("androidx.compose.material3.adaptive:adaptive:1.0.0")
// implementation("androidx.compose.material3.adaptive:adaptive-layout:1.0.0")
// implementation("androidx.compose.material3.adaptive:adaptive-navigation:1.0.0")
@Composable
fun PantallaProductos() {
val navigator = rememberListDetailPaneScaffoldNavigator<Producto>()
// Manejar el botón Back del sistema:
BackHandler(navigator.canNavigateBack()) {
navigator.navigateBack()
}
ListDetailPaneScaffold(
directive = navigator.scaffoldDirective,
value = navigator.scaffoldValue,
listPane = {
AnimatedPane {
ListaProductos(
onProductoClick = { producto ->
// Navegar al detalle — en teléfono hace push,
// en tablet simplemente muestra el panel derecho
navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, producto)
}
)
}
},
detailPane = {
AnimatedPane {
val producto = navigator.currentDestination?.contentKey
if (producto != null) {
DetalleProducto(producto = producto)
} else {
// Estado vacío cuando no hay producto seleccionado (solo en tablet)
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text("Seleccioná un producto")
}
}
}
}
)
}
Back navigation adaptativa
El comportamiento del botón Back difiere entre teléfono y tablet en el patrón list-detail:
// En TELÉFONO:
// → Lista (pantalla 1) → click → Detalle (pantalla 2)
// → Back desde Detalle → vuelve a Lista
// → Back desde Lista → sale de la app
// En TABLET:
// → Lista y Detalle se muestran simultáneamente
// → Back NO navega entre paneles (ya están visibles los dos)
// → Back sale de la pantalla completa
// El navigator maneja esto automáticamente:
BackHandler(navigator.canNavigateBack()) {
navigator.navigateBack()
}
// canNavigateBack() retorna true solo en teléfono cuando está en el detalle
// En tablet siempre retorna false (no hay back entre paneles)
AdaptiveScaffold completo con navegación
// El patrón más completo: navegación adaptativa + list-detail
@Composable
fun AppCompleta() {
var destinoActual by remember { mutableStateOf(Destino.PRODUCTOS) }
val navigator = rememberListDetailPaneScaffoldNavigator<Any>()
NavigationSuiteScaffold(
navigationSuiteItems = {
Destino.values().forEach { destino ->
item(
icon = { Icon(destino.icono, contentDescription = destino.label) },
label = { Text(destino.label) },
selected = destinoActual == destino,
onClick = { destinoActual = destino }
)
}
}
) {
// El contenido cambia según el destino activo
when (destinoActual) {
Destino.PRODUCTOS -> PantallaProductos() // usa ListDetailPaneScaffold
Destino.BUSCAR -> PantallaBuscar() // pantalla simple
Destino.PERFIL -> PantallaPerfil() // pantalla simple
}
}
}
SupportingPaneScaffold — panel de apoyo
Cuando el panel secundario no es "el detalle" sino información de apoyo (un panel de propiedades, filtros, chat lateral), SupportingPaneScaffold es la herramienta correcta:
@Composable
fun EditorConPropiedades() {
val navigator = rememberSupportingPaneScaffoldNavigator()
SupportingPaneScaffold(
directive = navigator.scaffoldDirective,
value = navigator.scaffoldValue,
mainPane = {
AnimatedPane {
AreaEdicion(
onMostrarPropiedades = {
navigator.navigateTo(SupportingPaneScaffoldRole.Supporting)
}
)
}
},
supportingPane = {
AnimatedPane {
PanelPropiedades(
onCerrar = { navigator.navigateBack() }
)
}
}
)
// En teléfono: el panel de propiedades aparece como overlay o pantalla separada
// En tablet: aparece a la derecha del área de edición permanentemente
}
BoxWithConstraints para casos custom
// Cuando los scaffolds no cubren tu caso específico,
// BoxWithConstraints da acceso directo al tamaño disponible:
@Composable
fun LayoutCustomAdaptativo() {
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
// maxWidth y maxHeight son el espacio disponible real
when {
maxWidth > 840.dp -> {
// Pantalla grande — layout de dos columnas
Row {
Column(modifier = Modifier.weight(1f)) { PanelIzquierdo() }
Column(modifier = Modifier.weight(1f)) { PanelDerecho() }
}
}
maxWidth > 600.dp -> {
// Pantalla media — layout de dos columnas compacto
Row {
Column(modifier = Modifier.weight(2f)) { PanelIzquierdo() }
Column(modifier = Modifier.weight(3f)) { PanelDerecho() }
}
}
else -> {
// Teléfono — layout de una columna
Column { PanelUnico() }
}
}
}
}
// BoxWithConstraints es más flexible pero también más manual que los scaffolds
// Usarlo cuando ningún scaffold existente cubre el caso