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

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

开发者在为可折叠设备(尤其是 Samsung Trifold 或原始 Pixel Fold 等以横向格式打开的设备 [rotation_0 = 横向])创建应用时,经常会遇到独特的困难。开发者错误包括:

  • 对设备屏幕方向的假设有误
  • 被忽略的应用场景
  • 无法在配置更改期间重新计算或缓存值

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

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

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

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

构建自适应

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

自适应布局

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

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

窗口大小类别

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

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

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

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

when {
  windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_EXPANDED_LOWER_BOUND) -> // Large
  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 并为您应用所有必要的转换(宽高比、缩放和旋转),简化了显示相机 Feed 的过程。

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

解决方案 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">

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

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

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

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

对于旋转矢量(用于指南针或 XR 应用),请使用 SensorManager.remapCoordinateSystem() 根据当前旋转情况将相机镜头方向或屏幕顶部映射到新轴。

应用兼容性

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

如需了解更多信息,请查看平台中提供的兼容性问题解决方法的完整列表,特别是与相机预览替换可能会改变应用行为的 Android 16 API 变更相关的问题解决方法。

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