将 CoordinatorLayout 迁移到 Compose

CoordinatorLayout 是一个 ViewGroup,可实现复杂的、重叠的嵌套布局。它用作容器,可实现特定的 Material Design 交互,例如展开/折叠工具栏和其所包含视图的底部抽屉式导航栏。

在 Compose 中,CoordinatorLayout 最接近的等效项是 ScaffoldScaffold 为将 Material Components 组合到常见屏幕模式和交互中提供内容槽。本页面介绍如何将 CoordinatorLayout 实现迁移到 Compose 中的 Scaffold

迁移步骤

要将 CoordinatorLayout 迁移到 Scaffold,请按以下步骤操作

  1. 在下面的代码段中,CoordinatorLayout 包含一个 AppBarLayout 用于包含 ToolBar、一个 ViewPager 和一个 FloatingActionButton。从您的 UI 层次结构中注释掉 CoordinatorLayout 及其子项,并添加一个 ComposeView 来替换它。

    <!--  <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. 在您的 Fragment 或 Activity 中,获取对您刚刚添加的 ComposeView 的引用,并对其调用 setContent 方法。在该方法的主体中,将 Scaffold 设置为其内容

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

  3. Scaffold 的内容中,添加屏幕的主要内容。因为上面 XML 中的主要内容是 ViewPager2,我们将使用 HorizontalPager,它是它的 Compose 等效项。Scaffoldcontent lambda 也会收到一个 PaddingValues 实例,该实例应应用于内容根。您可以使用 Modifier.padding 将相同的 PaddingValues 应用于 HorizontalPager

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

  4. 使用 Scaffold 提供的其他内容槽来添加更多屏幕元素并迁移剩余的子视图。您可以使用 topBar 槽来添加一个 TopAppBar,并使用 floatingActionButton 槽来提供一个 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 */ }
        }
    }

常见用例

折叠和展开工具栏

在视图系统中,要使用 CoordinatorLayout 折叠和展开工具栏,您可以使用 AppBarLayout 作为工具栏的容器。然后,您可以通过关联的可滚动视图(如 RecyclerViewNestedScrollView)上的 XML 中的 layout_behavior 指定一个 Behavior,以声明工具栏在滚动时如何折叠/展开。

在 Compose 中,您可以通过 TopAppBarScrollBehavior 实现类似的效果。例如,要实现一个折叠/展开的工具栏,使其在您向上滚动时显示,请按以下步骤操作

  1. 调用 TopAppBarDefaults.enterAlwaysScrollBehavior() 来创建 TopAppBarScrollBehavior
  2. 将创建的 TopAppBarScrollBehavior 提供给 TopAppBar
  3. 通过 Scaffold 上的 Modifier.nestedScroll 连接 NestedScrollConnection,以便 Scaffold 可以在可滚动内容向上/向下滚动时接收嵌套滚动事件。这样,包含的应用栏就可以在内容滚动时适当地折叠/展开。

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

自定义折叠/展开滚动效果

您可以为 enterAlwaysScrollBehavior 提供多个参数以自定义折叠/展开动画效果。TopAppBarDefaults 还提供其他 TopAppBarScrollBehavior,例如 exitUntilCollapsedScrollBehavior,它仅在内容完全向下滚动时展开应用栏。

要创建完全自定义的效果(例如,视差效果),您还可以创建自己的 NestedScrollConnection,并在内容滚动时手动偏移工具栏。有关代码示例,请参阅 AOSP 上的嵌套滚动示例

抽屉式导航栏

使用视图时,您可以通过将 DrawerLayout 用作根视图来实现导航抽屉式导航栏。反过来,您的 CoordinatorLayoutDrawerLayout 的子视图。DrawerLayout 还包含另一个子视图,例如 NavigationView,用于在抽屉式导航栏中显示导航选项。

在 Compose 中,您可以使用 ModalNavigationDrawer 可组合项来实现导航抽屉式导航栏。ModalNavigationDrawer 为抽屉式导航栏提供了一个 drawerContent 槽,为屏幕内容提供了一个 content 槽。

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

要了解详情,请参阅抽屉式导航栏

Snackbar

Scaffold 提供了一个 snackbarHost 槽,该槽可以接受一个 SnackbarHost 可组合项来显示 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
    // ...
}

要了解详情,请参阅Snackbar

了解详情

有关将 CoordinatorLayout 迁移到 Compose 的更多信息,请参阅以下资源