ListDetailPaneScaffold:列表与详情自动分合 · AndroidX 源码指南
AAndroidX 源码指南
自适应布局
自适应布局 · androidx.window

ListDetailPaneScaffold:列表与详情自动分合Window 1.6.0-alpha05

用 navigator 保留目的地,用 scaffold 根据窗口决定 pane 数量。

最后更新 2026-08-01

复制即用

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun MailScreen() {
    val scope = rememberCoroutineScope()
    val navigator = rememberListDetailPaneScaffoldNavigator<String>()

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        scaffoldState = navigator.scaffoldState,
        listPane = {
            AnimatedPane {
                MailList(onOpen = { id ->
                    scope.launch {
                        navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, id)
                    }
                })
            }
        },
        detailPane = {
            AnimatedPane {
                MailDetail(navigator.currentDestination?.contentKey)
            }
        },
    )
}

两个角色

角色职责
Navigator保存当前 pane 目的地、计算 scaffold 状态、响应导航
Scaffold按 directive 把 list/detail/extra pane 放好

核心概念:“选择了哪一项”必须独立于”当前显示几个 pane”:

  • 窄窗口:只显示当前 pane(详情覆盖列表)。
  • 宽窗口:同时显示列表 + 详情。
  • 状态由 navigator 管理,窗口变化不丢选择。

导航与返回

// 打开详情
scope.launch { navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, itemId) }

// 系统返回
BackHandler {
    if (navigator.canNavigateBack()) {
        navigator.navigateBack()          // 先:scaffold 内从详情回列表
    } else {
        // 再:交给应用外层 back stack
        onExit()
    }
}

canNavigateBack() 判断 scaffold 内能否返回(如详情回列表);能就先 navigateBack(),不能才交给外层。

SupportingPaneScaffold:主 + 辅助

需要”主内容 + 辅助内容”(如详情页里的辅助面板)时:

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun SupportingScaffold() {
    val navigator = rememberSupportingPaneScaffoldNavigator<Int>()
    SupportingPaneScaffold(
        directive = navigator.scaffoldDirective,
        scaffoldState = navigator.scaffoldState,
        mainPane = { MainPane() },
        supportingPane = { SupportingPane() },
    )
}

常见陷阱

  • 选择状态和 pane 数量耦合:窄屏详情覆盖列表后返回丢选择。
  • 返回处理不完整:不检查 canNavigateBack() 直接退出整个页面。
  • 忘 AnimatedPane:pane 切换无动画/布局突变。
  • 实验 API:当前版本带 ExperimentalMaterial3AdaptiveApi,升级 alpha 重核对。

复制即用

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ListDetailExample() {
    val scope = rememberCoroutineScope()
    val navigator = rememberListDetailPaneScaffoldNavigator<String>()

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

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        scaffoldState = navigator.scaffoldState,
        listPane = {
            AnimatedPane { ItemList(onSelect = { id ->
                scope.launch { navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, id) }
            }) }
        },
        detailPane = {
            AnimatedPane { ItemDetail(navigator.currentDestination?.contentKey) }
        },
    )
}

要点

  • Navigator 管状态,Scaffold 管布局;选择独立于 pane 数量。
  • 返回先 canNavigateBack()navigateBack(),再交外层。
  • 辅助内容用 SupportingPaneScaffold。
  • 实验 API,升级时重核对签名。

相关页面