Migrar o CoordinatorLayout para o Compose

CoordinatorLayout é um ViewGroup que permite layouts complexos, sobrepostos e aninhados. Ele é usado como um contêiner para ativar interações específicas do Material Design, como barras de ferramentas de expansão/recolhimento e páginas inferiores, para visualizações contidas dentro dele.

No Compose, o equivalente mais próximo de uma CoordinatorLayout é um Scaffold. Um Scaffold fornece slots de conteúdo para combinar componentes do Material Design em padrões e interações de tela comuns. Esta página descreve como migrar a implementação de CoordinatorLayout para usar o Scaffold no Compose.

Etapas da migração

Para migrar CoordinatorLayout para Scaffold, siga estas etapas:

  1. No snippet abaixo, o CoordinatorLayout contém um AppBarLayout para conter um ToolBar, um ViewPager e um FloatingActionButton. Comente a CoordinatorLayout e os filhos dela da hierarquia da interface e adicione uma ComposeView para substituí-la.

    <!--  <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. No fragmento ou na atividade, acesse uma referência ao ComposeView que você acabou de adicionar e chame o método setContent. No corpo do método, defina um Scaffold como o conteúdo:

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

  3. Adicione o conteúdo principal da tela no conteúdo da Scaffold. Como o conteúdo principal no XML acima é um ViewPager2, usaremos um HorizontalPager, que é o equivalente do Compose. A lambda content do Scaffold também recebe uma instância de PaddingValues que precisa ser aplicada à raiz do conteúdo. Você pode usar Modifier.padding para aplicar o mesmo PaddingValues ao HorizontalPager.

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

  4. Use outros slots de conteúdo fornecidos pelo Scaffold para adicionar mais elementos de tela e migrar as visualizações filhas restantes. É possível usar o slot topBar para adicionar um TopAppBar e o slot floatingActionButton para fornecer um 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 comuns

Recolher e expandir barras de ferramentas

No sistema de visualização, para recolher e expandir a barra de ferramentas com CoordinatorLayout, use um AppBarLayout como contêiner. Em seguida, você pode especificar uma Behavior a layout_behavior em XML na visualização rolável associada (como RecyclerView ou NestedScrollView) para declarar como a barra de ferramentas é recolhida/expandida à medida que você rola a tela.

No Compose, é possível conseguir um efeito semelhante usando um TopAppBarScrollBehavior. Por exemplo, para implementar uma barra de ferramentas de recolhimento/expansão para que ela apareça quando você rola para cima, siga estas etapas:

  1. Chame TopAppBarDefaults.enterAlwaysScrollBehavior() para criar um TopAppBarScrollBehavior.
  2. Forneça o TopAppBarScrollBehavior criado ao TopAppBar.
  3. Conecte o NestedScrollConnection via Modifier.nestedScroll no Scaffold para que o Scaffold possa receber eventos de rolagem aninhados à medida que o conteúdo rolável rola para cima/para baixo. Dessa forma, a barra de apps contida pode ser recolhida/expandida corretamente à medida que o conteúdo rola a tela.

    // 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 */
        // ...
    }

Personalizar o efeito de rolagem recolher/expandir

Você pode fornecer vários parâmetros ao enterAlwaysScrollBehavior para personalizar o efeito da animação de recolhimento/expansão. O TopAppBarDefaults também fornece outros TopAppBarScrollBehavior, como exitUntilCollapsedScrollBehavior, que só expandem a barra de apps quando o conteúdo é rolado para baixo totalmente.

Para criar um efeito totalmente personalizado (por exemplo, um efeito paralaxe), você também pode criar seu próprio NestedScrollConnection e deslocar a barra de ferramentas manualmente à medida que o conteúdo é rolado. Consulte o exemplo de rolagem aninhada no AOSP para ver um exemplo de código.

Gavetas

Com as visualizações, você implementa uma gaveta de navegação usando uma DrawerLayout como visualização raiz. O CoordinatorLayout, por sua vez, é uma visualização filha do DrawerLayout. O DrawerLayout também contém outra visualização filha, por exemplo, uma NavigationView, para mostrar as opções de navegação na gaveta.

No Compose, é possível implementar uma gaveta de navegação usando o elemento ModalNavigationDrawer combinável. O ModalNavigationDrawer oferece um slot drawerContent para a gaveta e um slot content para o conteúdo da tela.

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
        // ...
    }
}

Consulte Gavetas para saber mais.

Snackbars

Scaffold fornece um slot snackbarHost, que pode aceitar um elemento combinável SnackbarHost para mostrar um 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
    // ...
}

Consulte Snackbars para saber mais.

Saiba mais

Para mais informações sobre como migrar uma CoordinatorLayout para o Compose, consulte os recursos abaixo: