支持三折叠设备和横向折叠设备

一部处于闭合和完全展开状态的横屏可折叠设备,旁边是一部处于闭合和完全展开状态的三折叠设备。

开发者在为可折叠设备(尤其是三星 Trifold 或初代 Pixel Fold 等设备,这些设备以横向格式打开 [rotation_0 = landscape])创建应用时,经常会遇到独特的难题。开发者常犯的错误包括:

  • 对设备屏幕方向的假设错误
  • 忽略了用例
  • 未能重新计算或缓存配置变更中的值

与特定设备相关的问题包括:

  • 外屏和内屏之间的设备自然屏幕方向不匹配(基于 rotation_0 = 纵向的假设),导致应用在折叠和展开过程中失败
  • 屏幕密度不同,并且 density 配置变更处理不正确
  • 相机预览问题,原因是相机传感器依赖于自然屏幕方向

如需在可折叠设备上提供优质的用户体验,请重点关注以下关键领域:

  • 根据应用占用的实际屏幕区域确定应用的屏幕方向,而不是设备的物理屏幕方向
  • 更新相机预览以正确管理设备屏幕方向和宽高比,避免方向有误的预览,并防止图像拉伸或剪裁
  • 在设备折叠或展开期间保持应用连续性,方法是使用 ViewModel 或类似方法保留状态,或者手动处理屏幕密度变化和屏幕方向变化,这样可以避免应用重启或状态丢失
  • 对于使用运动传感器的应用,请调整坐标系以与屏幕的当前屏幕方向保持一致,并避免基于 rotation_0 = 纵向的假设,从而保证精确的用户互动

构建自适应应用

如果您的应用已是 自适应应用,并且符合自适应应用质量指南中概述的优化级别(第 2 级),则该应用应能在可折叠设备上正常运行。否则,在仔细检查三折叠设备和横向折叠设备的具体细节之前,请先查看以下基础 Android 自适应开发概念。

自适应布局

您的界面不仅必须处理不同的屏幕尺寸,还必须处理宽高比的实时变化,例如展开设备以及进入多窗口模式或桌面窗口化模式。如需进一步了解如何执行以下操作,请参阅自适应布局简介

  • 设计和实现自适应布局
  • 根据窗口大小调整应用的主导航
  • 使用窗口大小类别来调整应用的界面
  • 使用 Jetpack API 简化规范布局(例如列表详情)的实现
在打开的可折叠设备上,应用以信箱模式显示;在另一个打开的可折叠设备上,同一应用以全屏模式显示,并采用自适应布局。
图 1.非自适应布局(信箱模式)和自适应布局之间的区别。

窗口大小类别

可折叠设备(包括横向折叠设备和三折叠设备)可以在紧凑型、中型和展开型窗口大小类别之间即时切换。了解和实现这些类别可确保您的应用针对当前设备状态显示正确的导航组件和内容密度。

应用在尺寸为紧凑型、中等和扩展型窗口大小类的设备上的显示效果。
图 2.窗口大小类别。

以下示例使用 Material 3 自适应库来确定应用可用的 空间大小,方法是先调用 currentWindowAdaptiveInfo() 函数,然后针对三个窗口大小类别使用相应的 布局:

val adaptiveInfo = currentWindowAdaptiveInfo()
val windowSizeClass = adaptiveInfo.windowSizeClass

when {
  windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_EXPANDED_LOWER_BOUND) -> // Expanded
  windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_MEDIUM_LOWER_BOUND) -> // Medium
  else -> // Compact
}

如需了解详情,请参阅使用窗口大小类别

自适应应用质量

遵循第 2 级(自适应优化)第 1 级(自适应 差异化)自适应应用质量指南可确保您的应用 在三折叠设备、横向折叠设备 和其他大屏设备上提供引人入胜的用户体验。该指南涵盖多个级别的重要检查,以从自适应就绪状态过渡到差异化体验。

Android 16 及更高版本

对于以 Android 16(API 级别 36)及更高版本为目标平台的应用,系统会忽略最小宽度 >= 600dp 的显示屏上的屏幕方向、尺寸可调整性和宽高比限制。应用会填充整个显示窗口,无论 宽高比或用户偏好的屏幕方向如何,并且不再使用信箱模式 兼容性模式。

特别注意事项

三折叠设备和横向折叠设备引入了独特的硬件行为,需要进行特殊处理,尤其是在传感器、相机预览和配置连续性(在折叠、展开或调整大小时保留状态)方面。

相机预览

在横向可折叠设备或宽高比计算(在多窗口模式、窗口化模式或连接的显示屏等场景中)上,常见的问题是相机预览显示拉伸、方向有误、裁剪或旋转。

假设不匹配

此问题通常发生在大屏设备和可折叠设备上,因为应用可能会假定相机功能(例如宽高比和传感器方向)与设备功能(例如设备屏幕方向和自然屏幕方向)之间存在固定关系。

新的设备规格对这种假设提出了挑战。可折叠设备可以更改其显示大小和宽高比,而无需更改设备旋转。例如,展开设备会更改宽高比,但如果用户不旋转设备,其旋转角度将保持不变。如果应用假定宽高比与设备旋转相关,则可能会错误地旋转或缩放相机预览。如果应用假定相机传感器方向与纵向设备屏幕方向匹配,则可能会发生同样的情况,但对于横向折叠设备而言,情况并非总是如此。

解决方案 1:Jetpack CameraX(最佳)

最简单且最可靠的解决方案是使用 Jetpack CameraX 库。其 PreviewView 界面元素旨在自动处理所有预览复杂性 :

  • PreviewView 会正确调整传感器方向、设备旋转和缩放。
  • 它会保持相机图像的宽高比,通常通过居中和裁剪 (FILL_CENTER) 来实现。
  • 您可以根据需要将缩放类型设置为 FIT_CENTER,以信箱模式显示预览。

如需了解详情,请参阅 CameraX 文档中的实现预览

解决方案 2:CameraViewfinder

如果您使用的是现有的 Camera2 代码库,则 CameraViewfinder 库(向后兼容到 API 级别 21)是另一种现代解决方案。它通过使用 TextureViewSurfaceView 并为您应用所有必要的转换(宽高比、缩放和旋转),简化了摄像头画面的显示。

如需了解详情,请参阅相机取景器简介博文和 相机预览开发者指南。

解决方案 3:手动 Camera2 实现

如果您无法使用 CameraX 或 CameraViewfinder,则必须手动计算屏幕方向和宽高比,并确保在每次配置变更时更新计算结果:

  • CameraCharacteristics 获取相机传感器方向(例如 0、90、180、270 度)。
  • 获取设备的当前显示屏旋转角度(例如 0、90、180、270 度)。
  • 使用这两个值来确定 SurfaceViewTextureView 所需的转换。
  • 确保输出 Surface 的宽高比与相机预览的宽高比一致,以防止失真。
  • 相机应用可能在屏幕的一部分中运行,无论是在多窗口模式或桌面窗口化模式下,还是在连接的显示屏上。因此,不应使用屏幕尺寸来确定相机取景器的尺寸,而应使用窗口指标

如需了解详情,请参阅相机预览开发者指南和不同设备规格上的 相机应用视频。

解决方案 4:使用 intent 执行基本相机操作

如果您不需要太多相机功能,一个简单的解决方案是使用设备的默认相机应用执行拍照或录制视频等基本相机操作。您无需与相机库集成,而是 使用 intent

如需了解详情,请参阅相机 intent

配置和连续性

可折叠设备增强了界面的多功能性,但可能会比非可折叠设备启动更多配置变更。您的应用必须管理这些配置变更及其组合,例如设备旋转、折叠/展开即可及多窗口模式或桌面模式下的窗口大小调整,同时保留或恢复应用状态。例如,应用必须保持以下连续性:

  • 应用状态,不会崩溃或对用户造成破坏性更改(例如,在切换屏幕或将应用发送到后台时)
  • 可滚动字段的滚动位置
  • 输入文本字段的文字和键盘状态
  • 媒体播放位置,以便在配置变更启动时从中断播放的位置继续播放

经常触发的配置变更包括 screenSizesmallestScreenSizescreenLayoutorientationdensityfontScaletouchscreenkeyboard

请参阅 android:configChanges处理配置变更。如需详细了解如何管理应用状态,请参阅保存界面状态

密度配置变更

三折叠设备和横向折叠设备的外屏和内屏可能具有不同的像素密度。因此,管理 density 的配置变更需要格外注意。Android 通常会在显示屏密度发生变化时重启 activity,这可能会导致数据丢失。为防止系统重启 activity,请在清单中声明密度处理,并在应用中以编程方式管理配置变更。

AndroidManifest.xml 配置

  • density:声明应用将处理屏幕密度变化
  • 其他配置变更:最好也声明其他经常发生的配置变更,例如 screenSizeorientationkeyboardHiddenfontScale

声明密度(和其他配置变更)可防止系统重启 activity,而是调用 onConfigurationChanged()。

onConfigurationChanged() 实现

发生密度变化时,您必须在回调中更新资源(例如重新加载位图或重新计算布局大小):

  • 验证 DPI 是否已更改为 newConfig.densityDpi
  • 将自定义视图、自定义可绘制对象等重置为新密度

要处理的资源项

  • 图片资源:将位图和可绘制对象替换为特定于密度的 资源,或直接调整缩放比例
  • 布局单位(dp 到 px 转换):重新计算视图大小、边距、 内边距
  • 字体和文字大小:重新应用 sp 单位文字大小
  • 自定义 View/Canvas 绘制:更新用于 绘制 Canvas 的基于像素的值

确定应用屏幕方向

构建自适应应用时,切勿依赖实体设备旋转,因为它在大屏设备上会忽略它,并且多窗口模式下的应用可能具有与设备不同的屏幕方向。相反,请使用 Configuration.orientation 或 WindowMetrics 来根据窗口大小确定应用当前是处于横向还是纵向。

解决方案 1:使用 Configuration.orientation

此属性用于标识应用当前显示的屏幕方向。

解决方案 2:使用 WindowMetrics#getBounds()

您可以获取应用的当前显示边界,并检查其宽度和高度以确定屏幕方向。

如果您需要限制应用在手机(或可折叠设备的外屏)上的屏幕方向,但不限制其在大屏设备上的屏幕方向,请参阅限制应用在手机上的屏幕方向

姿态和显示模式

纵向折叠设备和横向折叠设备都支持可折叠设备的姿态和状态,例如桌面模式和 HALF_OPENED 都 支持。不过,三折叠设备不支持桌面模式,也不能使用 HALF_OPENED。三折叠设备在完全展开时提供更大的屏幕,从而带来独特的用户体验。

如需在支持 HALF_OPENED 的可折叠设备上区分您的应用,请使用 Jetpack WindowManager API,例如 FoldingFeature

如需详细了解可折叠设备的姿态、状态和对相机预览的支持,请参阅以下开发者指南:

可折叠设备可提供独特的观看体验。借助后置显示屏模式和双屏幕模式,您可以为可折叠设备构建特殊的显示功能,例如后置摄像头自拍预览以及内外屏的同步显示功能。如需了解详情,请参阅:

将屏幕方向锁定为自然传感器方向

对于非常特定的用例(尤其是需要接管整个屏幕且与设备的折叠状态无关的应用),nosensor 标志可让您将应用锁定为设备的自然屏幕方向。例如,在 Pixel Fold 上,设备折叠时的自然屏幕方向为纵向,而展开时的自然屏幕方向为横向。添加 nosensor 标志会强制应用在外部显示屏上运行时锁定为纵向,在内屏上运行时锁定为横向。

<activity
  android:name=".MainActivity"
  android:screenOrientation="nosensor">

游戏和扩展现实传感器重新映射

对于游戏和扩展现实应用,原始传感器数据(例如陀螺仪或加速度计)是在设备固定的坐标系中提供的。如果用户旋转设备以横向玩游戏,传感器轴不会随屏幕旋转,从而导致游戏控件不正确。

如需解决此问题,请检查当前的 Display.getRotation() 并相应地重新映射轴:

  • 旋转 0:x=x,y=y
  • 旋转 90:x=-y,y=x
  • 旋转 180:x=-x,y=-y
  • 旋转 270:x=y,y=-x

对于旋转矢量(用于罗盘或扩展现实应用),请使用 SensorManager.remapCoordinateSystem() 根据当前旋转将相机镜头方向或 屏幕顶部映射到新轴。

应用兼容性

应用必须遵循应用质量指南,以确保在所有设备规格和连接的显示屏上都具有兼容性。如果应用无法遵守该指南,设备制造商可以实现兼容性处理,但这可能会降低用户体验。

如需了解详情,请查看平台中提供的兼容性 变通方案的完整列表,特别是与相机 预览替换Android 16 API 变更相关的变通方案,这些变更可能会 改变应用的行为。

如需详细了解如何构建自适应应用,请参阅自适应应用质量 指南