Cómo migrar CoordinatorLayout a Compose

CoordinatorLayout es un ViewGroup que habilita diseños complejos, superpuestos y anidados. Se usa como contenedor para habilitar interacciones específicas de Material Design, como barras de herramientas de expansión o contracción y hojas inferiores, para las vistas que contiene.

En Compose, el equivalente más cercano a un CoordinatorLayout es un Scaffold. Un Scaffold proporciona ranuras de contenido para combinar componentes de Material en interacciones y patrones de pantalla comunes. En esta página, se describe cómo migrar tu implementación de CoordinatorLayout para usar Scaffold en Compose.

Pasos de la migración

Para migrar CoordinatorLayout a Scaffold, sigue estos pasos:

  1. En el siguiente fragmento, CoordinatorLayout contiene un AppBarLayout que contiene un ToolBar, un ViewPager y un FloatingActionButton. Marca como comentario CoordinatorLayout y sus elementos secundarios desde la jerarquía de tu IU y agrega un ComposeView para reemplazarlos.

    <!--  <androidx.coordinatorlayout.widget.CoordinatorLayout-->
    <!--      android:id="@+id/coordinator_layout"-->
    <!--      android:layout_width="match_parent"-->
    <!--      android:layout_height="match_parent"-->
    <!--      android:fitsSystemWindows="true">-->
    
    <!--    <androidx.compose.ui.platform.ComposeView-->
    <!--        android:id="@+id/compose_view"-->
    <!--        android:layout_width="match_parent"-->
    <!--        android:layout_height="match_parent"-->
    <!--        app:layout_behavior="@string/appbar_scrolling_view_behavior" />-->
    
    <!--    <com.google.android.material.appbar.AppBarLayout-->
    <!--        android:id="@+id/app_bar_layout"-->
    <!--        android:layout_width="match_parent"-->
    <!--        android:layout_height="wrap_content"-->
    <!--        android:fitsSystemWindows="true"-->
    <!--        android:theme="@style/Theme.Sunflower.AppBarOverlay">-->
    
        <!-- AppBarLayout contents here -->
    
    <!--    </com.google.android.material.appbar.AppBarLayout>-->
    
    <!--  </androidx.coordinatorlayout.widget.CoordinatorLayout>-->
    
    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/compose_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    
  2. En tu fragmento o actividad, obtén una referencia al ComposeView que acabas de agregar y llama al método setContent en él. En el cuerpo del método, establece un Scaffold como su contenido:

    composeView.setContent {
        Scaffold(Modifier.fillMaxSize()) { contentPadding ->
            // Scaffold contents
            // ...
        }
    }

  3. En el contenido de tu Scaffold, agrega el contenido principal de la pantalla. Como el contenido principal del XML anterior es un ViewPager2, usaremos un HorizontalPager, que es su equivalente en Compose. La lambda content de Scaffold también recibe una instancia de PaddingValues que se debe aplicar a la raíz del contenido. Puedes usar Modifier.padding para aplicar el mismo PaddingValues a HorizontalPager.

    composeView.setContent {
        Scaffold(Modifier.fillMaxSize()) { contentPadding ->
            val pagerState = rememberPagerState {
                10
            }
            HorizontalPager(
                state = pagerState,
                modifier = Modifier.padding(contentPadding)
            ) { /* Page contents */ }
        }
    }

  4. Usa otros espacios de contenido que proporciona Scaffold para agregar más elementos de pantalla y migrar las vistas secundarias restantes. Puedes usar el espacio topBar para agregar un TopAppBar y el floatingActionButton para proporcionar un FloatingActionButton.

    composeView.setContent {
        Scaffold(
            Modifier.fillMaxSize(),
            topBar = {
                TopAppBar(
                    title = {
                        Text("My App")
                    }
                )
            },
            floatingActionButton = {
                FloatingActionButton(
                    onClick = { /* Handle click */ }
                ) {
                    Icon(
                        Icons.Filled.Add,
                        contentDescription = "Add Button"
                    )
                }
            }
        ) { contentPadding ->
            val pagerState = rememberPagerState {
                10
            }
            HorizontalPager(
                state = pagerState,
                modifier = Modifier.padding(contentPadding)
            ) { /* Page contents */ }
        }
    }

Casos de uso comunes

Contraer y expandir barras de herramientas

En el sistema de View, para contraer y expandir la barra de herramientas con CoordinatorLayout, debes usar un AppBarLayout como contenedor de la barra de herramientas. Luego, puedes especificar un objeto Behavior con layout_behavior en XML en la vista desplazable asociada (como RecyclerView o NestedScrollView) para declarar cómo se contrae o se expande la barra de herramientas a medida que te desplazas.

En Compose, puedes lograr un efecto similar con un TopAppBarScrollBehavior. Por ejemplo, para implementar una barra de herramientas que se contrae o se expande de modo que aparezca cuando te desplaces hacia arriba, sigue estos pasos:

  1. Llama a TopAppBarDefaults.enterAlwaysScrollBehavior() para crear un TopAppBarScrollBehavior.
  2. Proporciona el TopAppBarScrollBehavior creado a TopAppBar.
  3. Conecta NestedScrollConnection a través de Modifier.nestedScroll en Scaffold para que Scaffold pueda recibir eventos de desplazamiento anidados a medida que el contenido desplazable se desplaza hacia arriba o hacia abajo. De esta manera, la barra de la app contenida puede contraerse o expandirse adecuadamente a medida que se desplaza el contenido.

    // 1. Create the TopAppBarScrollBehavior
    val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
    
    Scaffold(
        topBar = {
            TopAppBar(
                title = {
                    Text("My App")
                },
                // 2. Provide scrollBehavior to TopAppBar
                scrollBehavior = scrollBehavior
            )
        },
        // 3. Connect the scrollBehavior.nestedScrollConnection to the Scaffold
        modifier = Modifier
            .fillMaxSize()
            .nestedScroll(scrollBehavior.nestedScrollConnection)
    ) { contentPadding ->
        /* Contents */
        // ...
    }

Cómo personalizar el efecto de desplazamiento que se contrae o se expande

Puedes proporcionar varios parámetros para enterAlwaysScrollBehavior para personalizar el efecto de la animación que se contrae o se expande. TopAppBarDefaults también proporciona otros TopAppBarScrollBehavior, como exitUntilCollapsedScrollBehavior, que solo expande la barra de la app cuando el contenido se desplaza hacia abajo.

Para crear un efecto completamente personalizado (por ejemplo, un efecto de paralaje), también puedes crear tu propio NestedScrollConnection y desplazar la barra de herramientas manualmente a medida que se desplaza el contenido. Consulta la muestra de desplazamiento anidado en AOSP para ver un ejemplo de código.

Paneles laterales

Con Views, implementas un panel lateral de navegación con un DrawerLayout como vista raíz. A su vez, tu CoordinatorLayout es una vista secundaria de DrawerLayout. El DrawerLayout también contiene otra vista secundaria, como NavigationView, para mostrar las opciones de navegación en el panel lateral.

En Compose, puedes implementar un panel lateral de navegación con el elemento componible ModalNavigationDrawer. ModalNavigationDrawer ofrece una ranura drawerContent para el panel lateral y una ranura content para el contenido de la pantalla.

ModalNavigationDrawer(
    drawerContent = {
        ModalDrawerSheet {
            Text("Drawer title", modifier = Modifier.padding(16.dp))
            Divider()
            NavigationDrawerItem(
                label = { Text(text = "Drawer Item") },
                selected = false,
                onClick = { /*TODO*/ }
            )
            // ...other drawer items
        }
    }
) {
    Scaffold(Modifier.fillMaxSize()) { contentPadding ->
        // Scaffold content
        // ...
    }
}

Consulta Paneles laterales para obtener más información.

Barras de notificaciones

Scaffold proporciona un espacio snackbarHost, que puede aceptar un elemento SnackbarHost componible para mostrar un Snackbar.

val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(
    snackbarHost = {
        SnackbarHost(hostState = snackbarHostState)
    },
    floatingActionButton = {
        ExtendedFloatingActionButton(
            text = { Text("Show snackbar") },
            icon = { Icon(Icons.Filled.Image, contentDescription = "") },
            onClick = {
                scope.launch {
                    snackbarHostState.showSnackbar("Snackbar")
                }
            }
        )
    }
) { contentPadding ->
    // Screen content
    // ...
}

Consulta Snackbars para obtener más información.

Más información

Si quieres obtener más información para migrar un CoordinatorLayout a Compose, consulta los siguientes recursos: