构建辅助窗格布局

辅助窗格布局可让用户专注于应用的主要内容,同时显示相关的辅助信息。例如,主窗格可能会显示有关电影的详细信息,而辅助窗格会列出类似的电影、同一导演执导的电影或由同一演员出演的电影。

如需了解详情,请参阅 Material 3 辅助窗格指南

实现具有基架的辅助窗格

NavigableSupportingPaneScaffold 是一个可组合项,可简化在 Jetpack Compose 中实现辅助窗格布局的过程。它封装了 SupportingPaneScaffold,并添加了内置导航和预测性返回 处理功能。

辅助窗格基架最多支持三个窗格:

  • 主窗格:显示主要内容。
  • 辅助窗格:提供与 主窗格相关的其他背景信息或工具。
  • 额外窗格(可选):用于在需要时显示补充内容。

基架会根据窗口大小进行调整:

  • 在大型窗口中,主窗格和辅助窗格并排显示。
  • 在小型窗口中,一次仅显示一个窗格,并随着用户 导航而切换。

    主要内容占据显示屏的大部分区域,辅助内容位于旁边。
    图 1.辅助窗格布局。

添加依赖项

NavigableSupportingPaneScaffoldMaterial 3 自适应布局 库的一部分。

将以下三个相关依赖项添加到应用或模块的 build.gradle 文件中:

Kotlin

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

Groovy

implementation 'androidx.compose.material3.adaptive:adaptive'
implementation 'androidx.compose.material3.adaptive:adaptive-layout'
implementation 'androidx.compose.material3.adaptive:adaptive-navigation'
  • 自适应:底层智能模块,例如HingeInfoPosture

  • adaptive-layout:自适应布局,例如 ListDetailPaneScaffoldSupportingPaneScaffold

  • adaptive-navigation:用于在窗格内和窗格之间导航的可组合项,以及默认支持导航的自适应布局,例如 NavigableListDetailPaneScaffoldNavigableSupportingPaneScaffold

验证您的项目是否包含 compose-material3-adaptive 1.1.0-beta1 或更高版本。

选择启用预测性返回手势

如需在 Android 15 或更低版本中启用预测性返回动画,您必须选择启用对预测性返回手势的支持。如需选择启用,请将 android:enableOnBackInvokedCallback="true"添加到<application>标记或 各个<activity>标记内的AndroidManifest.xml文件中。

一旦您的应用以 Android 16(API 级别 36)或更高版本为目标平台,预测性返回功能就会默认启用。

创建导航器

在小型窗口中,一次仅显示一个窗格,因此请使用 ThreePaneScaffoldNavigator在窗格之间移动。创建导航器的实例 使用 rememberSupportingPaneScaffoldNavigator

val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator()
val scope = rememberCoroutineScope()

将导航器传递给基架

基架需要 ThreePaneScaffoldNavigator,这是一个表示基架状态的接口 ,即 ThreePaneScaffoldValuePaneScaffoldDirective

NavigableSupportingPaneScaffold(
    navigator = scaffoldNavigator,
    mainPane = { /*...*/ },
    supportingPane = { /*...*/ },
)

主窗格和辅助窗格是包含您的内容的可组合项。在导航期间,使用 AnimatedPane 应用默认窗格动画。使用 基架值检查辅助窗格是否处于隐藏状态;如果是, 则显示一个按钮,该按钮会调用 navigateTo(SupportingPaneScaffoldRole.Supporting) 以显示 辅助窗格。

对于大型屏幕,请使用 ThreePaneScaffoldNavigator.navigateBack() 方法关闭辅助窗格,并传入 BackNavigationBehavior.PopUntilScaffoldValueChange 常量。调用此 方法会强制重组NavigableSupportingPaneScaffold。 在重组期间,检查 ThreePaneScaffoldNavigator.currentDestination 属性以确定 是否显示辅助窗格。

以下是基架的完整实现:

val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator()
val scope = rememberCoroutineScope()
val backNavigationBehavior = BackNavigationBehavior.PopUntilScaffoldValueChange

NavigableSupportingPaneScaffold(
    navigator = scaffoldNavigator,
    mainPane = {
        AnimatedPane(
            modifier = Modifier
                .safeContentPadding()
                .background(Color.Red)
        ) {
            if (scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting] == PaneAdaptedValue.Hidden) {
                Button(
                    modifier = Modifier
                        .wrapContentSize(),
                    onClick = {
                        scope.launch {
                            scaffoldNavigator.navigateTo(SupportingPaneScaffoldRole.Supporting)
                        }
                    }
                ) {
                    Text("Show supporting pane")
                }
            } else {
                Text("Supporting pane is shown")
            }
        }
    },
    supportingPane = {
        AnimatedPane(modifier = Modifier.safeContentPadding()) {
            Column {
                // Allow users to dismiss the supporting pane. Use back navigation to
                // hide an expanded supporting pane.
                if (scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting] == PaneAdaptedValue.Expanded) {
                    // Material design principles promote the usage of a right-aligned
                    // close (X) button.
                    IconButton(
                        modifier =  Modifier.align(Alignment.End).padding(16.dp),
                        onClick = {
                            scope.launch {
                                scaffoldNavigator.navigateBack(backNavigationBehavior)
                            }
                        }
                    ) {
                        Icon(Icons.Default.Close, contentDescription = "Close")
                    }
                }
                Text("Supporting pane")
            }

        }
    }
)

提取窗格可组合项

SupportingPaneScaffold 的各个窗格提取到各自的可组合项中,使其可重复使用和可测试。如果您需要默认动画,请使用ThreePaneScaffoldScope 访问 AnimatedPane

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ThreePaneScaffoldPaneScope.MainPane(
    shouldShowSupportingPaneButton: Boolean,
    onNavigateToSupportingPane: () -> Unit,
    modifier: Modifier = Modifier,
) {
    AnimatedPane(
        modifier = modifier.safeContentPadding()
    ) {
        // Main pane content
        if (shouldShowSupportingPaneButton) {
            Button(onClick = onNavigateToSupportingPane) {
                Text("Show supporting pane")
            }
        } else {
            Text("Supporting pane is shown")
        }
    }
}

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ThreePaneScaffoldPaneScope.SupportingPane(
    scaffoldNavigator: ThreePaneScaffoldNavigator<Any>,
    modifier: Modifier = Modifier,
    backNavigationBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilScaffoldValueChange,
) {
    val scope = rememberCoroutineScope()
    AnimatedPane(modifier = Modifier.safeContentPadding()) {
        Column {
            // Allow users to dismiss the supporting pane. Use back navigation to
            // hide an expanded supporting pane.
            if (scaffoldNavigator.scaffoldValue[SupportingPaneScaffoldRole.Supporting] == PaneAdaptedValue.Expanded) {
                // Material design principles promote the usage of a right-aligned
                // close (X) button.
                IconButton(
                    modifier =  modifier.align(Alignment.End).padding(16.dp),
                    onClick = {
                        scope.launch {
                            scaffoldNavigator.navigateBack(backNavigationBehavior)
                        }
                    }
                ) {
                    Icon(Icons.Default.Close, contentDescription = "Close")
                }
            }
            Text("Supporting pane")
        }

    }
}

将窗格提取到可组合项中可简化 SupportingPaneScaffold 的使用(将以下内容与上一部分中基架的完整实现进行比较):

val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator()
val scope = rememberCoroutineScope()

NavigableSupportingPaneScaffold(
    navigator = scaffoldNavigator,
    mainPane = {
        MainPane(
            shouldShowSupportingPaneButton = scaffoldNavigator.scaffoldValue.secondary == PaneAdaptedValue.Hidden,
            onNavigateToSupportingPane = {
                scope.launch {
                    scaffoldNavigator.navigateTo(ThreePaneScaffoldRole.Secondary)
                }
            }
        )
    },
    supportingPane = { SupportingPane(scaffoldNavigator = scaffoldNavigator) },
)

如果您需要对基架的特定方面进行更多控制,请考虑使用 SupportingPaneScaffold而不是NavigableSupportingPaneScaffold。这 会分别接受 PaneScaffoldDirectiveThreePaneScaffoldValueThreePaneScaffoldState。这种灵活性可让您为窗格间距实现自定义逻辑,并确定应同时显示多少个窗格。您还可以通过添加 ThreePaneScaffoldPredictiveBackHandler 来启用预测性返回支持。

添加 ThreePaneScaffoldPredictiveBackHandler

附加预测性返回处理程序,该处理程序会接受基架导航器实例并指定 backBehavior。这决定了在返回导航期间如何从返回堆栈中弹出目标。然后,将 scaffoldDirectivescaffoldState 传递给 SupportingPaneScaffold。使用接受 ThreePaneScaffoldState 的重载,并传入 scaffoldNavigator.scaffoldState

SupportingPaneScaffold 中定义主窗格和辅助窗格。使用 AnimatedPane 实现默认窗格动画。

完成这些步骤后,您的代码应类似于以下内容:

val scaffoldNavigator = rememberSupportingPaneScaffoldNavigator()
val scope = rememberCoroutineScope()

ThreePaneScaffoldPredictiveBackHandler(
    navigator = scaffoldNavigator,
    backBehavior = BackNavigationBehavior.PopUntilScaffoldValueChange
)

SupportingPaneScaffold(
    directive = scaffoldNavigator.scaffoldDirective,
    scaffoldState = scaffoldNavigator.scaffoldState,
    mainPane = {
        MainPane(
            shouldShowSupportingPaneButton = scaffoldNavigator.scaffoldValue.secondary == PaneAdaptedValue.Hidden,
            onNavigateToSupportingPane = {
                scope.launch {
                    scaffoldNavigator.navigateTo(ThreePaneScaffoldRole.Secondary)
                }
            }
        )
    },
    supportingPane = { SupportingPane(scaffoldNavigator = scaffoldNavigator) },
)