Cómo compilar un diseño de detalles de lista

Lista-detalles es un patrón de IU que consiste en un diseño de panel doble en el que un panel presenta una lista de elementos y otro muestra los detalles de los elementos seleccionados de la lista.

El patrón es particularmente útil para aplicaciones que proporcionan información detallada sobre elementos de grandes colecciones, por ejemplo, un cliente de correo electrónico que tiene una lista de correos electrónicos y el contenido detallado de cada mensaje de correo electrónico. También se puede usar para rutas menos críticas, como dividir las preferencias de una app en una lista de categorías con las preferencias para cada categoría en el panel de detalles.

Implementa el patrón de IU con ListDetailPaneScaffold

ListDetailPaneScaffold es un elemento componible que simplifica la implementación del patrón de lista-detalles en tu app. Un andamiaje de lista-detalles puede constar de hasta tres paneles: uno de lista, uno de detalles y un panel adicional opcional. El andamiaje controla los cálculos del espacio de pantalla. Cuando hay un tamaño de pantalla suficiente, se muestra el panel de detalles junto al panel de lista. En tamaños de pantalla pequeños, el andamiaje cambia automáticamente a mostrar la lista o el panel de detalles en pantalla completa.

Se muestra un panel de detalles junto a la página de lista.
Figura 1: Cuando haya suficiente tamaño de pantalla disponible, se mostrará el panel de detalles junto al panel de lista.
Después de seleccionar un elemento, el panel de detalles ocupa toda la pantalla.
Figura 2: Cuando el tamaño de la pantalla es limitado, el panel de detalles (desde que se seleccionó un elemento) ocupa todo el espacio.

Cómo declarar dependencias

ListDetailPaneScaffold es parte de la biblioteca adaptable de Material 3. Agrega una dependencia para la biblioteca en el archivo build.gradle de tu app o módulo:

implementation("androidx.compose.material3.adaptive:adaptive:1.0.0-alpha07")
implementation("androidx.compose.material3.adaptive:adaptive-layout:1.0.0-alpha07")
implementation("androidx.compose.material3.adaptive:adaptive-navigation:1.0.0-alpha07")

Uso básico

A continuación, se muestra el uso básico de ListDetailPaneScaffold:

  1. Almacena el elemento seleccionado actualmente de la lista en una variable de estado mutable. La variable contiene el elemento que se mostrará en el panel de detalles. Por lo general, es recomendable inicializar el elemento seleccionado actualmente con null, lo que indica que aún no se realizó ninguna selección.

    class MyItem(val id: Int) {
        companion object {
            val Saver: Saver<MyItem?, Int> = Saver(
                { it?.id },
                ::MyItem,
            )
        }
    }

    var selectedItem: MyItem? by rememberSaveable(stateSaver = MyItem.Saver) {
        mutableStateOf(null)
    }

  2. Crea el ThreePaneScaffoldNavigator con rememberListDetailPaneScaffoldNavigator y agrega un BackHandler. Este navegador se usa para moverse entre la lista, los detalles y los paneles adicionales, y proporcionar el estado al andamiaje. El elemento BackHandler agregado admite la navegación hacia atrás con el botón o el gesto atrás del sistema. El comportamiento esperado del botón Atrás para un objeto ListDetailPaneScaffold depende del tamaño de la ventana y del valor actual del andamiaje. Si ListDetailPaneScaffold admite la recuperación con el estado actual, canNavigateBack() será true, lo que habilitará BackHandler.

    val navigator = rememberListDetailPaneScaffoldNavigator<Nothing>()
    
    BackHandler(navigator.canNavigateBack()) {
        navigator.navigateBack()
    }

  3. Pasa el scaffoldState de la navigator que creaste al elemento ListDetailPaneScaffold componible.

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        // ...
    )

  4. Proporciona la implementación del panel de lista a ListDetailPaneScaffold. Asegúrate de que tu implementación incluya un argumento de devolución de llamada para capturar el elemento recién seleccionado. Cuando se active esta devolución de llamada, actualiza la variable de estado selectedItem y usa ThreePaneScaffoldNavigator para mostrar el panel de detalles (ListDetailPaneScaffoldRole.Detail).

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            AnimatedPane(Modifier) {
                MyList(
                    onItemClick = { id ->
                        // Set current item
                        selectedItem = id
                        // Switch focus to detail pane
                        navigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
                    }
                )
            }
        },
        // ...
    )

  5. Incluye la implementación del panel de detalles en ListDetailPaneScaffold. Muestra el contenido de los detalles solo si el selectedItem no es nulo.

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane =
        // ...
        detailPane = {
            AnimatedPane(Modifier) {
                selectedItem?.let { item ->
                    MyDetails(item)
                }
            }
        },
    )

Después de implementar los pasos anteriores, tu código debería ser similar al siguiente:

// Currently selected item
var selectedItem: MyItem? by rememberSaveable(stateSaver = MyItem.Saver) {
    mutableStateOf(null)
}

// Create the ListDetailPaneScaffoldState
val navigator = rememberListDetailPaneScaffoldNavigator<Nothing>()

BackHandler(navigator.canNavigateBack()) {
    navigator.navigateBack()
}

ListDetailPaneScaffold(
    directive = navigator.scaffoldDirective,
    value = navigator.scaffoldValue,
    listPane = {
        AnimatedPane(Modifier) {
            MyList(
                onItemClick = { id ->
                    // Set current item
                    selectedItem = id
                    // Display the detail pane
                    navigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
                },
            )
        }
    },
    detailPane = {
        AnimatedPane(Modifier) {
            // Show the detail pane content if selected item is available
            selectedItem?.let { item ->
                MyDetails(item)
            }
        }
    },
)