adaptive

adaptive

热门

用于制作或更新应用 UI 的说明,使其适应不同的 Android 设备,包括手机、平板、折叠屏、笔记本电脑、台式机、电视、汽车和 XR。包括如何使用 Compose MediaQuery API 处理不同的窗口大小、指点设备(如鼠标)和文本输入设备(如键盘)。还涵盖使用 Navigation3 Scenes 的多窗格布局、具有不同目标大小的自适应 UI 组件(如按钮),以及使用 Compose Grid 和 FlexBox API 的自适应布局(包括导航区域 - 导航栏和导航栏)。

7092Star
460Fork
更新于 2026/8/29
SKILL.md
只读
名称
adaptive
描述

用于制作或更新应用 UI 的说明,使其适应不同的 Android 设备,包括手机、平板、折叠屏、笔记本电脑、台式机、电视、汽车和 XR。包括如何使用 Compose MediaQuery API 处理不同的窗口大小、指点设备(如鼠标)和文本输入设备(如键盘)。还涵盖使用 Navigation3 Scenes 的多窗格布局、具有不同目标大小的自适应 UI 组件(如按钮),以及使用 Compose Grid 和 FlexBox API 的自适应布局(包括导航区域 - 导航栏和导航栏)。

先决条件

应用必须:

  • 所有屏幕都使用 Compose。如果仍在使用 Fragments 或 Views,建议使用 XML 到 Compose 技能迁移这些屏幕。
  • 使用 Jetpack Navigation 3。如果没有,建议使用 Navigation 3 技能迁移应用。

使应用自适应的流程

要使应用自适应,请遵循以下步骤或其子集以适应任务。

  • 步骤 1:验证当前 UI
  • 步骤 2:使导航栏自适应
  • 步骤 3:添加多窗格布局
  • 步骤 4:通过更改列数使垂直列表自适应
  • 步骤 5:滚动时隐藏应用栏

步骤 1. 验证当前 UI

确保存在截图测试以验证不同形态因素下的当前 UI。如果不存在,请添加 Compose Preview 截图测试工具。使用以下注解为所有主要形态因素创建预览。例如:

@Preview(name = "Phone", device = Devices.PHONE, showBackground = true)
@Preview(name = "Foldable", device = Devices.FOLDABLE, showBackground = true)
@Preview(name = "Tablet", device = Devices.TABLET, showBackground = true)
@Preview(name = "Desktop", device = Devices.DESKTOP, showBackground = true)
annotation class FormFactorPreviews

@PreviewTest
@FormFactorPreviews
@Composable
fun FeedScreenPreview() {
    SnippetsTheme {
        Box {
            Text("My Screen")
        }
    }
}

<br />

步骤 2. 使导航栏自适应

底部导航栏针对用户手持手机竖屏时的触摸输入进行了优化。在较大的手持设备(如平板和展开的折叠屏)上,导航区域必须可从屏幕边缘访问(导航栏)。

如果需要为内容提供更多屏幕空间,请隐藏导航区域。示例包括:

  • 当用户向下滚动时隐藏导航栏,向上滚动时再次显示。假设是当用户向下滚动时,他们在消费内容,但向上滚动时他们试图导航离开该内容。
  • 当导航区域的内容分散注意力时隐藏它。例如,在相机预览或显示全屏照片时。

当详情屏幕在移动设备上全屏显示时,必须在较大屏幕上停用全屏模式。

迁移步骤:

  • 找到现有的导航栏。
  • 将每个项目转换为 NavigationSuiteItem
  • 确定导航栏的可见性是否发生变化。例如,如果它被 AnimatedContentAnimatedVisibility 组合函数包裹。如果是,请遵循“控制导航区域可见性”中的指导。
  • 将包含导航栏的容器(通常是 Scaffold)替换为 Material 3 自适应布局库中的 NavigationSuiteScaffold
  • 使用 NavigationSuiteScaffoldnavigationItems 参数提供导航项目。

步骤 2.1. 控制导航区域可见性

如果导航栏的可见性发生变化 - 在某些场景或某些屏幕上隐藏 - 必须使用自适应导航区域保持此行为。这通过 NavigationSuiteScaffoldstate 参数完成。

迁移步骤:

  • 确定导航栏隐藏的场景。这通常使用布尔变量表示可见性。使用 isNavBarVisibleshouldShowNavBar 作为变量名。
  • 使用 rememberNavigationSuiteScaffoldState() 创建 NavigationSuiteScaffoldState 实例,并将其传递给 NavigationSuiteScaffold
  • 当导航区域可见性发生变化时,使用 LaunchedEffect 调用 NavigationSuiteScaffoldState 上的 showhide

例如:

// 将此变量传递给任何需要控制导航区域可见性的组合函数
var isNavBarVisible by remember { mutableStateOf(true) }
val scaffoldVisibilityState = rememberNavigationSuiteScaffoldState()

NavigationSuiteScaffold(
    navigationSuiteItems = navItems,
    state = scaffoldVisibilityState
) {
    // 主要内容
}

LaunchedEffect(isNavBarVisible){
    if (isNavBarVisible) {
        scaffoldVisibilityState.show()
    } else {
        scaffoldVisibilityState.hide()
    }
}

<br />

步骤 3. 使用 Navigation 3 Scenes 添加多窗格布局

分析代码库,查找相关屏幕 - 在一个屏幕上点击某物会打开另一个显示与第一个相关信息的屏幕。有两种典型的屏幕关系:列表-详情和支持窗格。

重要提示:必须使用 Navigation 3 SceneStrategy 方法实现多窗格布局。不要使用 ListDetailPaneScaffoldSupportingPaneScaffold

步骤 3.1. 列表-详情

识别列表和详情屏幕

列表-详情布局显示项目列表(这是列表屏幕),点击项目会打开一个新屏幕,显示该项目的更多详细信息(详情屏幕)。

典型用途包括生产力应用,如电子邮件、笔记和消息。

除非明确要求,否则当详情内容需要大量屏幕空间(例如,受益于全屏呈现的图像或媒体)时,避免使用此模式。

添加 Material 列表-详情 SceneStrategy
  • 添加 androidx.compose.material3.adaptive:adaptive-navigation3
  • 使用 rememberListDetailSceneStrategy 创建 androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy
  • 使用其 sceneStrategies 参数将 ListDetailSceneStrategy 传递给 NavDisplay
使用元数据标识列表和详情屏幕
  • 使用 entry(metadata = ...)NavEntry(metadata = ...) 向列表条目添加元数据,使用 ListDetailSceneStrategy.listPane(detailPlaceholder = { <占位符组合函数> })
  • 使用 detailPlaceholder 参数在未选择列表项时在详情屏幕上添加占位符。
  • 使用 ListDetailSceneStrategy.detailPane() 向详情条目添加元数据。
重要注意事项
  • 当详情屏幕在移动设备上全屏显示其内容(内容填满整个屏幕,栏或导轨隐藏)时,如果它是列表-详情布局的一部分,则必须停用全屏模式。
  • 在列表-详情布局中,详情屏幕不得显示返回箭头。

有关参考实现,请查看 Nav3 Material 列表详情配方

步骤 3.2. 支持窗格

识别支持窗格屏幕,其中主屏幕显示单个项目,选择它会打开一个带有更多详细信息的“支持屏幕”。支持屏幕补充主屏幕,并显示在支持窗格中。

添加 Material 支持窗格 SceneStrategy
  • 如果尚未添加,请添加 androidx.compose.material3.adaptive:adaptive-navigation3
  • 使用 rememberSupportingPaneSceneStrategy 创建 androidx.compose.material3.adaptive.navigation3.SupportingPaneSceneStrategy
  • 使用其 sceneStrategies 参数将 SupportingPaneSceneStrategy 传递给 NavDisplay
使用元数据标识主屏幕和支持屏幕
  • 使用 entry(metadata = ...)NavEntry(metadata = ...) 向主条目添加元数据,使用 SupportingPaneSceneStrategy.mainPane()
  • 使用 SupportingPaneSceneStrategy.supportingPane() 向支持条目添加元数据

步骤 3.3. 运行截图测试

如果进行了更改,请记录新的参考文件。请用户目视验证新布局是否正确。

步骤 4. 通过更改列数使垂直列表自适应

步骤 4.1. 使惰性列表自适应

查找以下垂直列表组合函数:LazyColumnLazyVerticalGridLazyVerticalStaggeredGrid

迁移步骤:

  • 为列选择合适的最小宽度(以 dp 为单位)。项目必须在此宽度下对用户清晰可见。
  • 对于 LazyColumn:更改为 LazyVerticalGrid 并遵循后面的说明
  • 对于 LazyVerticalGrid:将 columns 参数更改为使用 GridCells.Adaptive(<width>.dp)
  • 对于 LazyVerticalStaggeredGrid:将 columns 参数更改为使用 StaggeredGridCells.Adaptive(<width>.dp)

步骤 4.2. 将非惰性列表迁移到 Grid

警告:Grid 是 Compose 1.11.0-beta01 中提供的实验性 API。请与用户确认他们愿意在代码库中使用实验性 API。

查找包含多个相同类型项目的任何 Column,并将其替换为 Grid。不要将其替换为 LazyVerticalGrid 或任何其他惰性布局。不要将 Grid 放在现有的 Column 内。完全替换它。

Grid 通过向其 config 参数提供 lambda(GridConfigurationScope 上的扩展函数)进行配置。在 lambda 内部,constraints 提供网格容器的最小和最大尺寸,可用于根据可用大小更改行数和列数。例如,以下代码配置 Grid,使得当可用宽度为:

  • 小于 800dp 时,使用 2x4 网格
  • 800dp 或更大时,使用 4x2 网格
Grid(
    config = {
        val maxWidthDp = constraints.maxWidth.toDp()
        val (cols, rows) = if (maxWidthDp < 800.dp){
            2 to 4
        } else{
            4 to 2
        }

        val gapSizeDp = 8.dp
        val cellSize = ((maxWidthDp - (gapSizeDp * (cols - 1))) / cols).coerceAtLeast(0.dp)
        repeat(cols) { column(cellSize) }
        repeat(rows) { row(cellSize) }
        gap(gapSizeDp)
    }
) { /** items **/ }

<br />

Grid 是实验性 API,因此请向使用它的任何函数添加 @OptIn(ExperimentalGridApi::class) 注解。

步骤 5:滚动时隐藏应用栏

在具有多个顶级目标的应用中,每个屏幕必须独立管理自己的应用栏状态。有两种主要的滚动行为:

  • exitUntilCollapsedScrollBehavior:向下滚动时隐藏,向上滚动时保持隐藏,直到到达最顶部(偏移量为 0)。
  • enterAlwaysScrollBehavior:向下滚动时隐藏,向上滚动时立即显示。

最后一步:构建和测试

构建应用并运行本地测试。如果项目有截图测试,请运行它们,但不要更新参考图像。提示用户在查看截图差异后执行此操作。

实验性自适应 API 的其他文档

以下 API 可从 Compose 1.11.0-beta01 获得。

FlexBox

查看 FlexBox 文档:

MediaQuery

当需要查询设备的屏幕尺寸、指针精度、键盘类型、是否有摄像头或麦克风以及其他设备功能时,请查看 MediaQuery 文档

Grid

当需要在网格布局中显示固定数量的项目时,请查看 Grid 文档: