其他
Android 应用需要适配手机、平板、折叠屏、桌面、TV…
在 Jetpack Compose 与 Navigation 3 的 Android 应用中,将 UI 改造为能按窗口尺寸、输入设备和多窗格场景自适应的界面。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 35/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 39/100 ×0.15
适用场景
Android 应用需要适配手机、平板、特定
解决思路
- Step 1:确保存在截图测试,用于验证当前 UI 在不同 form factor 上的表现;若不存在,加入 Compose Preview Screenshot Testing tool。
- Step 1:用 @Preview 为主流 form factor 创建预览:Devices.PHONE、Devices.FOLDABLE、Devices.TABLET、Devices.DESKTOP;可定义 @FormFactorPreviews 注解并在 @PreviewTest 预览函数上使用。
- Step 2:定位现有导航栏。
- Step 2:把每个导航项转换为 NavigationSuiteItem。
- Step 2:判断导航栏可见性是否会变化,例如是否包裹在 AnimatedContent 或 AnimatedVisibility 中;如果会,按 Step 2.1 处理。
- Step 2:用 Material 3 adaptive layouts 库中的 NavigationSuiteScaffold 替换承载导航栏的容器,通常是 Scaffold。
- Step 2:通过 NavigationSuiteScaffold 的 navigationItems 参数提供导航项。
- Step 2:在大屏手持设备如平板、展开折叠屏上使用可从屏幕边缘访问的 navigation rail;底部导航栏仅适合手机竖屏触摸输入。
- Step 2:若需为内容提供更多屏幕空间,隐藏导航区域;例如向下滚动时隐藏导航栏、向上滚动时显示;或在相机预览、全屏照片等导航区域内容干扰时隐藏。
- Step 2:手机端详情页全屏时,在更大屏幕上必须停用全屏模式。
- Step 2.1:识别导航栏被隐藏的场景,通常用布尔变量记录可见性,例如 isNavBarVisible 或 shouldShowNavBar。
- Step 2.1:用 rememberNavigationSuiteScaffoldState() 创建 NavigationSuiteScaffoldState,并传给 NavigationSuiteScaffold 的 state 参数。
- Step 2.1:当导航区域可见性变化时,用 LaunchedEffect 调用 NavigationSuiteScaffoldState 的 show 或 hide。
- Step 3:分析代码库,找出相关屏幕:点击一个屏幕中的元素会打开另一个显示相关信息的屏幕。
- Step 3:识别两类典型关系:list-detail 与 supporting pane。
- Step 3:必须使用 Navigation 3 的 SceneStrategy 方式实现多窗格布局;不要使用 ListDetailPaneScaffold 或 SupportingPaneScaffold。
- Step 3.1:识别 list 和 detail 屏幕:list 屏幕显示项目列表,点击项目打开显示该项目信息的 detail 屏幕。
- Step 3.1:典型用于 email、notes、messaging 等生产力应用;除非明确要求,当 detail 内容需要大量屏幕空间,如图片或媒体更适合全屏展示时,避免该模式。
- Step 3.1:添加 androidx.compose.material3.adaptive:adaptive-navigation3 库。
- Step 3.1:用 rememberListDetailSceneStrategy 创建 androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy。
- Step 3.1:通过 NavDisplay 的 sceneStrategies 参数传入 ListDetailSceneStrategy。
- Step 3.1:用 entry(metadata = ...) 或 NavEntry(metadata = ...) 给 list entry 添加元数据,使用 ListDetailSceneStrategy.listPane(detailPlaceholder = { <placeholder composable> })。
- Step 3.1:用 detailPlaceholder 参数在未选择 list 项时给 detail 屏幕添加占位内容。
- Step 3.1:用 ListDetailSceneStrategy.detailPane() 给 detail entry 添加元数据。
- Step 3.1:当 detail 屏幕在手机上全屏显示,内容填满整屏、bars 或 rails 隐藏,且属于 list-detail 布局时,必须停用全屏模式。
- Step 3.1:在 list-detail 布局中,detail 屏幕不得显示返回箭头。
- Step 3.1:参考 Nav3 Material List Detail recipe,文件为 references/android/guide/navigation/navigation-3/recipes/material-listdetail.md。
- Step 3.2:识别 supporting pane 屏幕:主屏幕显示单个项目,选择后打开显示更多详情的 supporting screen;supporting screen 补充主屏幕,并显示在 supporting pane 中。
- Step 3.2:如果尚未添加,添加 androidx.compose.material3.adaptive:adaptive-navigation3 库。
- Step 3.2:用 rememberSupportingPaneSceneStrategy 创建 androidx.compose.material3.adaptive.navigation3.SupportingPaneSceneStrategy。
- Step 3.2:通过 NavDisplay 的 sceneStrategies 参数传入 SupportingPaneSceneStrategy。
- Step 3.2:用 entry(metadata = ...) 或 NavEntry(metadata = ...) 给 main entry 添加元数据,使用 SupportingPaneSceneStrategy.mainPane()。
- Step 3.2:用 SupportingPaneSceneStrategy.supportingPane() 给 supporting entry 添加元数据。
- Step 3.3:如果做了修改,记录新的参考文件;请用户目视确认新布局正确。
- Step 4.1:查找垂直列表 composable:LazyColumn、LazyVerticalGrid、LazyVerticalStaggeredGrid。
- Step 4.1:为列选择合适的最小宽度 dp;项目在该宽度下必须清晰可见。
- Step 4.1:对于 LazyColumn,改为 LazyVerticalGrid,并按后续说明处理。
- Step 4.1:对于 LazyVerticalGrid,把 columns 参数改为 GridCells.Adaptive(<width>.dp)。
- Step 4.1:对于 LazyVerticalStaggeredGrid,把 columns 参数改为 StaggeredGridCells.Adaptive(<width>.dp)。
- Step 4.2:注意 Grid 是实验性 API,从 Compose 1.11.0-beta01 起可用;先与用户确认是否愿意在代码库中使用实验性 API。
- Step 4.2:查找包含多个同类型项目的 Column,并用 Grid 替换;不要替换为 LazyVerticalGrid 或其他 lazy 布局;不要把 Grid 放进现有 Column;要完全替换。
- Step 4.2:通过 config 参数传入 lambda,即 GridConfigurationScope 的扩展函数,来配置 Grid;在 lambda 内用 constraints 获取网格容器最小和最大尺寸,并据此改变行数和列数。
- Step 4.2:例如当可用宽度小于 800dp 时使用 2x4 网格,大于等于 800dp 时使用 4x2 网格;按列和行设置 cellSize 并设置 gap。
- Step 4.2:Grid 为实验性 API,任何使用它的函数都要加 @OptIn(ExperimentalGridApi::class)。
- Step 5:在包含多个顶级 destination 的应用中,每个屏幕必须独立管理自己的 app bar 状态。
- Step 5:可选择 exitUntilCollapsedScrollBehavior:向下滚动隐藏,向上滚动保持隐藏,直到到达最顶部即 0 offset。
- Step 5:可选择 enterAlwaysScrollBehavior:向下滚动隐藏,向上滚动立即显示。
- Final:构建应用并运行本地测试。
- Final:如果有截图测试,运行它们,但不要更新参考图;等用户查看截图差异后再提示用户更新。
- 附加文档:从 Compose 1.11.0-beta01 起可用实验性自适应 API。
- FlexBox:查阅 FlexBox 文档,包括 Overview、Get started - setup、Set container behavior、Set item behavior,对应 references/android/develop/ui/compose/layouts/adaptive/flexbox/index.md、get-started.md、container-behavior.md、item-behavior.md。
- MediaQuery:当需要查询设备屏幕尺寸、指针精度、键盘类型、是否有摄像头或麦克风及其他设备能力时,查阅 MediaQuery 文档 references/android/develop/ui/compose/layouts/adaptive/mediaquery/index.md。
- Grid:当需要以网格布局显示固定数量项目时,查阅 Grid 文档,包括 Overview、Get started - setup、Set container properties、Set item properties,对应 references/android/develop/ui/compose/layouts/adaptive/grid/index.md、get-started.md、container-properties.md、item-properties.md。
适用边界 · 注意事项
- 应用尚未在所有屏幕使用 Compose,仍使用 Fragments 或 Views 时,不要直接按本技能改造;先使用 XML to Compose 技能迁移。
- 应用尚未使用 Jetpack Navigation 3 时,不要直接实现基于 Navigation 3 SceneStrategy 的多窗格布局;先使用 Navigation 3 技能迁移。
- 无法接受 Compose 1.11.0-beta01 的实验性 API 时,不要执行 Step 4.2 的 Grid,以及 FlexBox 和 Grid 实验 API 相关部分;普通自适应步骤仍可执行。