构建列表详情布局

列表详情是一种由双窗格布局组成的界面模式,其中一个窗格显示项目列表,另一个窗格显示从列表中选择的项的详细信息。

此模式特别适用于提供有关大型集合中元素的详细信息的应用,例如,具有电子邮件列表和每封电子邮件的详细内容的电子邮件客户端。列表详情还可用于不太重要的路径,例如将应用偏好设置划分为类别列表,并在详情窗格中包含每个类别的偏好设置。

使用 ListDetailPaneScaffold 实现界面模式

ListDetailPaneScaffold 是一个可组合项,用于简化应用中“列表-详情”模式的实现。列表详情 Scaffold 最多可由三个窗格组成:列表窗格、详情窗格和可选的额外窗格。基架负责处理屏幕空间计算。当有足够屏幕尺寸时,详情窗格会显示在列表窗格旁边。在小屏设备上,基架会自动切换为全屏显示列表窗格或详情窗格。

列表页面旁边显示的详细信息窗格。
图 1. 当有可用的屏幕尺寸时,详情窗格会显示在列表窗格旁边。
选择某项内容后,详情窗格会覆盖整个屏幕。
图 2. 当屏幕尺寸有限时,详情窗格(因为已选择项目)会占据整个空间。

声明依赖项

ListDetailPaneScaffoldMaterial 3 自适应库的一部分。在应用或模块的 build.gradle 文件中添加该库的依赖项:

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

基本用法

下面演示了 ListDetailPaneScaffold 的基本用法:

  1. 将列表中当前选定的项存储在可变状态变量中。该变量用于存储要在详情窗格中显示的项。通常,您会想要使用 null 初始化当前所选项,表示尚未做任何选择。

    class MyItem(val id: Int) {
        companion object {
            val Saver: Saver<MyItem?, Int> = Saver(
                { it?.id },
                ::MyItem,
            )
        }
    }

    var selectedItem: MyItem? by rememberSaveable(stateSaver = MyItem.Saver) {
        mutableStateOf(null)
    }

  2. 使用 rememberListDetailPaneScaffoldNavigator 创建 ThreePaneScaffoldNavigator,并添加 BackHandler。此导航器用于在列表窗格、详情窗格和额外窗格之间移动,并向基架提供状态。添加的 BackHandler 支持使用系统返回手势或按钮进行返回。ListDetailPaneScaffold 的返回按钮的预期行为取决于窗口大小和当前的基架值。如果 ListDetailPaneScaffold 可以支持返回当前状态,那么 canNavigateBack() 将为 true,从而启用 BackHandler

    val navigator = rememberListDetailPaneScaffoldNavigator<Nothing>()
    
    BackHandler(navigator.canNavigateBack()) {
        navigator.navigateBack()
    }

  3. scaffoldState 从您创建的 navigator 传递给 ListDetailPaneScaffold 可组合项。

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        // ...
    )

  4. 将您的列表窗格实现提供给 ListDetailPaneScaffold。请确保您的实现包含用于捕获新选定项的回调参数。触发此回调时,请更新 selectedItem 状态变量,并使用 ThreePaneScaffoldNavigator 显示详细信息窗格 (ListDetailPaneScaffoldRole.Detail)。

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            AnimatedPane(Modifier) {
                MyList(
                    onItemClick = { id ->
                        // Set current item
                        selectedItem = id
                        // Switch focus to detail pane
                        navigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
                    }
                )
            }
        },
        // ...
    )

  5. ListDetailPaneScaffold 中添加详细信息窗格实现。 仅当 selectedItem 不为 null 时,才会显示详情内容。

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane =
        // ...
        detailPane = {
            AnimatedPane(Modifier) {
                selectedItem?.let { item ->
                    MyDetails(item)
                }
            }
        },
    )

实现上述步骤后,您的代码应如下所示:

// Currently selected item
var selectedItem: MyItem? by rememberSaveable(stateSaver = MyItem.Saver) {
    mutableStateOf(null)
}

// Create the ListDetailPaneScaffoldState
val navigator = rememberListDetailPaneScaffoldNavigator<Nothing>()

BackHandler(navigator.canNavigateBack()) {
    navigator.navigateBack()
}

ListDetailPaneScaffold(
    directive = navigator.scaffoldDirective,
    value = navigator.scaffoldValue,
    listPane = {
        AnimatedPane(Modifier) {
            MyList(
                onItemClick = { id ->
                    // Set current item
                    selectedItem = id
                    // Display the detail pane
                    navigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
                },
            )
        }
    },
    detailPane = {
        AnimatedPane(Modifier) {
            // Show the detail pane content if selected item is available
            selectedItem?.let { item ->
                MyDetails(item)
            }
        }
    },
)