将 CoordinatorLayout 迁移到 Compose

CoordinatorLayout 是一个 ViewGroup,它支持复杂、重叠和嵌套的布局。 它用作容器,以支持对其中包含的视图进行特定的 Material Design 交互,例如展开/折叠工具栏和底部工作表。

在 Compose 中,CoordinatorLayout 最接近的等效项是 ScaffoldScaffold 提供内容插槽,用于将 Material 组件组合成常见的屏幕模式和交互。 此页面介绍了如何迁移您的 CoordinatorLayout 实现以在 Compose 中使用 Scaffold

迁移步骤

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

  1. 在下面的代码片段中,CoordinatorLayout 包含一个用于包含 ToolBarAppBarLayout、一个 ViewPager 和一个 FloatingActionButton。 将 CoordinatorLayout 及其子元素从您的 UI 层次结构中注释掉,并添加一个 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 中的 ViewPager2Scaffoldcontent 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 作为工具栏的容器。 然后,您可以通过 layout_behavior 在 XML 中指定关联的可滚动视图(如 RecyclerViewNestedScrollView)上的 Behavior,以声明工具栏在您滚动时如何折叠/展开。

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

  1. 调用 TopAppBarDefaults.enterAlwaysScrollBehavior() 以创建 TopAppBarScrollBehavior
  2. 将创建的 TopAppBarScrollBehavior 提供给 TopAppBar
  3. 通过 Modifier.nestedScrollScaffold 上连接 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))
            Divider()
            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 的更多信息,请参阅以下资源