构建自适应导航

大多数应用都有几个可通过应用的主要导航界面访问的顶级目的地。在较小的窗口(例如标准手机显示屏)中,目的地通常显示在窗口底部的导航栏中。在展开的窗口(例如平板电脑上的全屏应用)中,与应用一起显示侧边导航栏通常是更好的选择,因为在握住设备的左侧和右侧时,更容易触及导航控件。

NavigationSuiteScaffold 会根据 WindowSizeClass 显示适当的导航界面可组合项,从而简化在导航界面之间切换的操作。这包括在运行时窗口大小发生变化时动态更改界面。默认行为是显示以下任一界面组件:

  • 如果宽度或高度较小,或者设备处于桌上模式,则显示导航栏
  • 侧边导航栏(适用于所有其他内容)
图 1. NavigationSuiteScaffold 会在较小的窗口中显示导航栏。
图 2. NavigationSuiteScaffold 会在展开的窗口中显示侧边导航栏。

添加依赖项

NavigationSuiteScaffoldMaterial3 自适应导航套件库的一部分。在应用或模块的 build.gradle 文件中为该库添加依赖项:

Kotlin

implementation("androidx.compose.material3:material3-adaptive-navigation-suite")

Groovy

implementation 'androidx.compose.material3:material3-adaptive-navigation-suite'

创建框架

NavigationSuiteScaffold 的两个主要部分是导航套件项和所选目的地的相关内容。您可以在可组合项中直接定义导航套件项,但通常是在其他位置(例如枚举)中定义这些项:

enum class AppDestinations(
    @StringRes val label: Int,
    val icon: ImageVector,
    @StringRes val contentDescription: Int
) {
    HOME(R.string.home, Icons.Default.Home, R.string.home),
    FAVORITES(R.string.favorites, Icons.Default.Favorite, R.string.favorites),
    SHOPPING(R.string.shopping, Icons.Default.ShoppingCart, R.string.shopping),
    PROFILE(R.string.profile, Icons.Default.AccountBox, R.string.profile),
}

如需使用 NavigationSuiteScaffold,您必须跟踪当前目的地,您可以使用 rememberSaveable 执行此操作:

var currentDestination by rememberSaveable { mutableStateOf(AppDestinations.HOME) }

在以下示例中,navigationSuiteItems 参数(类型为 NavigationSuiteScope)使用其 item 函数来定义各个目的地的导航界面。目的地界面可在导航栏、侧边导航栏和抽屉式导航栏中使用。如需创建导航项,您需要循环遍历 AppDestinations(在上面的代码段中定义):

NavigationSuiteScaffold(
    navigationSuiteItems = {
        AppDestinations.entries.forEach {
            item(
                icon = {
                    Icon(
                        it.icon,
                        contentDescription = stringResource(it.contentDescription)
                    )
                },
                label = { Text(stringResource(it.label)) },
                selected = it == currentDestination,
                onClick = { currentDestination = it }
            )
        }
    }
) {
    // TODO: Destination content.
}

在目标内容 lambda 中,使用 currentDestination 值来确定要显示的界面。如果您在应用中使用导航库,请在此处使用该库来显示适当的目的地。当满足以下条件时,只需使用 when 语句即可:

NavigationSuiteScaffold(
    navigationSuiteItems = { /*...*/ }
) {
    // Destination content.
    when (currentDestination) {
        AppDestinations.HOME -> HomeDestination()
        AppDestinations.FAVORITES -> FavoritesDestination()
        AppDestinations.SHOPPING -> ShoppingDestination()
        AppDestinations.PROFILE -> ProfileDestination()
    }
}

更改颜色

NavigationSuiteScaffold 会在支架占用的整个区域(通常是整个窗口)上创建 Surface。此外,该框架还会绘制特定的导航界面,例如 NavigationBar。Surface 和导航界面都使用应用主题中指定的值,但您可以替换主题值。

containerColor 参数用于指定 Surface 的颜色。默认值是配色方案的背景颜色。contentColor 参数用于指定该 Surface 的内容颜色。默认值为为 containerColor 指定的任何内容的“开启”颜色。例如,如果 containerColor 使用 background 颜色,则 contentColor 使用 onBackground 颜色。如需详细了解颜色系统的运作方式,请参阅 Compose 中的 Material Design 3 主题设置。替换这些值时,请使用主题中定义的值,以便您的应用支持深色和浅色显示模式:

NavigationSuiteScaffold(
    navigationSuiteItems = { /* ... */ },
    containerColor = MaterialTheme.colorScheme.primary,
    contentColor = MaterialTheme.colorScheme.onPrimary,
) {
    // Content...
}

导航界面会在 NavigationSuiteScaffold Surface 前面绘制。界面颜色的默认值由 NavigationSuiteDefaults.colors() 提供,但您也可以替换这些值。例如,如果您希望导航栏的背景是透明的,但其他值为默认值,请替换 navigationBarContainerColor

NavigationSuiteScaffold(
    navigationSuiteItems = { /* ... */ },
    navigationSuiteColors = NavigationSuiteDefaults.colors(
        navigationBarContainerColor = Color.Transparent,
    )
) {
    // Content...
}

最终,您可以自定义导航界面中的每个项。调用 item 函数时,您可以传入 NavigationSuiteItemColors 的实例。该类用于指定导航栏、侧边栏和抽屉式导航栏中项的颜色。这意味着,您可以为每种导航界面类型使用相同的颜色,也可以根据需要更改颜色。在 NavigationSuiteScaffold 级别定义颜色,以便为所有项使用相同的对象实例,并调用 NavigationSuiteDefaults.itemColors() 函数以仅替换要更改的颜色:

val myNavigationSuiteItemColors = NavigationSuiteDefaults.itemColors(
    navigationBarItemColors = NavigationBarItemDefaults.colors(
        indicatorColor = MaterialTheme.colorScheme.primaryContainer,
        selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer
    ),
)

NavigationSuiteScaffold(
    navigationSuiteItems = {
        AppDestinations.entries.forEach {
            item(
                icon = {
                    Icon(
                        it.icon,
                        contentDescription = stringResource(it.contentDescription)
                    )
                },
                label = { Text(stringResource(it.label)) },
                selected = it == currentDestination,
                onClick = { currentDestination = it },
                colors = myNavigationSuiteItemColors,
            )
        }
    },
) {
    // Content...
}

自定义导航栏类型

NavigationSuiteScaffold 的默认行为会根据窗口大小类更改导航界面。不过,您可能希望替换此行为。例如,如果您的应用为 Feed 显示单个大内容窗格,则该应用可以为展开的窗口使用永久性抽屉导航栏,但仍会针对紧凑和中等窗口大小类别回退到默认行为:

val adaptiveInfo = currentWindowAdaptiveInfo()
val customNavSuiteType = with(adaptiveInfo) {
    if (windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.EXPANDED) {
        NavigationSuiteType.NavigationDrawer
    } else {
        NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo)
    }
}

NavigationSuiteScaffold(
    navigationSuiteItems = { /* ... */ },
    layoutType = customNavSuiteType,
) {
    // Content...
}

其他资源