用于制作或更新应用 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。 - 确定导航栏的可见性是否发生变化。例如,如果它被
AnimatedContent或AnimatedVisibility组合函数包裹。如果是,请遵循“控制导航区域可见性”中的指导。 - 将包含导航栏的容器(通常是
Scaffold)替换为 Material 3 自适应布局库中的NavigationSuiteScaffold。 - 使用
NavigationSuiteScaffold的navigationItems参数提供导航项目。
步骤 2.1. 控制导航区域可见性
如果导航栏的可见性发生变化 - 在某些场景或某些屏幕上隐藏 - 必须使用自适应导航区域保持此行为。这通过 NavigationSuiteScaffold 的 state 参数完成。
迁移步骤:
- 确定导航栏隐藏的场景。这通常使用布尔变量表示可见性。使用
isNavBarVisible或shouldShowNavBar作为变量名。 - 使用
rememberNavigationSuiteScaffoldState()创建NavigationSuiteScaffoldState实例,并将其传递给NavigationSuiteScaffold。 - 当导航区域可见性发生变化时,使用
LaunchedEffect调用NavigationSuiteScaffoldState上的show或hide。
例如:
// 将此变量传递给任何需要控制导航区域可见性的组合函数
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 方法实现多窗格布局。不要使用 ListDetailPaneScaffold 或 SupportingPaneScaffold。
步骤 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. 使惰性列表自适应
查找以下垂直列表组合函数:LazyColumn、LazyVerticalGrid、LazyVerticalStaggeredGrid。
迁移步骤:
- 为列选择合适的最小宽度(以 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 文档:






