连接的显示屏可将桌面窗口化模式体验扩展到标准手机,让用户可以通过移动设备访问大屏幕。此功能为应用互动和用户工作效率带来了新的可能性。
桌面窗口化的所有独特功能都适用于外接显示屏。将手机连接到显示屏后,手机状态保持不变,连接的显示屏上会启动一个空白桌面会话。手机与显示屏将作为两个独立的系统运行,每个显示屏上的应用各自运行。
如果您将启用了窗口化模式的设备(例如平板电脑)连接到外接显示器,桌面会话就会扩展到这两个显示屏上。这两个显示屏相当于一个无缝衔接的整体。在此设置下,窗口、内容和光标都可以在两屏之间自由移动。
为了有效地支持关联的显示屏,您需要注意应用设计和实现的多个方面。以下最佳实践可确保顺畅高效的用户体验。
处理动态显示变更
许多应用在构建时都假设 Display 对象及其特征在应用的生命周期内不会发生变化。不过,当用户连接或断开外部显示器,甚至在显示屏之间移动应用窗口时,与应用上下文或窗口关联的底层 Display 对象可能会改变。显示屏的属性(例如尺寸、分辨率、刷新率、HDR 支持和密度)可能各不相同。例如,如果您根据手机屏幕状况对值进行硬编码,布局很可能会在外部显示屏上显示异常。
外部显示屏可能具有截然不同的像素密度。您需要确保应用能够正确响应密度变化。这包括为布局使用密度无关像素 (dp)、提供密度特定的资源,并确保界面可适当缩放。
如果某个 activity 在外接显示屏断开连接时正在该显示屏上运行,系统会将该 activity 移至主显示屏。此移动会触发配置更改(例如屏幕尺寸和密度更改),这可能会导致重新创建 activity。您的应用必须通过保存和恢复界面状态来处理配置更改,以防止数据丢失或造成令人困惑的用户体验。
使用正确的上下文
在多屏幕环境中,使用适当的上下文至关重要。访问资源时,activity 上下文(已显示)与应用上下文(未显示)不同。
activity 上下文包含有关显示屏的信息,且始终会针对该 activity 所在的显示屏区域进行调整。这样,您便可以获得有关应用显示屏密度或窗口指标的正确信息。请始终使用 activity 上下文(或其他基于界面的上下文)来获取有关当前窗口或显示屏的信息。此外,这还会影响某些使用上下文提供的信息的系统 API。
在 Jetpack Compose 中,您可以使用 CompositionLocal 对象(例如 LocalConfiguration.current 和 LocalDensity.current)访问特定于显示屏的信息。当 activity 或窗口在不同显示屏之间移动时,设备配置会发生变化,从而触发使用新显示指标进行的重组。CompositionLocal 对象可让界面实现无缝自适应。
获取显示信息
您可以使用 Display 类获取显示大小、密度或标志等信息。使用 DisplayManager 系统服务获取可用显示屏。如需识别外部显示屏,请过滤掉 Display.DEFAULT_DISPLAY,这通常是内置的手机或平板电脑屏幕:
val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val displays = displayManager.getDisplays()
// The default display is 0. External displays have other IDs.
val externalDisplays = displays.filter { it.displayId != Display.DEFAULT_DISPLAY }
管理 activity 启动和配置
借助连接的显示屏,应用可以指定在启动或创建其他 activity 时,应用应在哪个显示屏上运行。此行为取决于清单文件以及 intent 标志和选项(由启动 activity 的实体设置)中定义的 activity 启动模式。
当 activity 移至辅助显示屏时,您的应用可能会经历上下文更新、窗口大小调整以及配置和资源更改。如果由该 activity 来处理配置更改,它会在 onConfigurationChanged() 中收到通知。否则,系统会重新启动该 activity。
如果为 activity 选择的启动模式支持多个实例,那么在辅助屏幕上启动将会创建一个新的 activity 实例。这两个 activity 会同时恢复,这对于某些多任务处理场景非常有用。
您可以使用 ActivityOptions 在特定显示屏上启动 activity。请注意,launchDisplayId 需要 Android 8(API 级别 26)或更高版本。
// Get DisplayManager and find the first external display.
val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val externalDisplayId = displayManager.displays
.firstOrNull { it.displayId != Display.DEFAULT_DISPLAY }
?.displayId
// If an external display is found, launch the activity on it.
if (externalDisplayId != null) {
val intent = Intent(this, MySecondaryActivity::class.java)
val options = ActivityOptions.makeBasic()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
options.launchDisplayId = externalDisplayId
}
startActivity(intent, options.toBundle())
} else {
// Optionally, handle the case where no external display is connected.
}
避免使用设备许可名单
应用有时会通过许可名单或检查 BUILD.MODEL 和内置显示大小,将大屏界面和功能限制为仅在部分设备上可用。此方法不适用于连接的显示屏,因为几乎任何设备都可以连接到大屏幕,并且在连接外部显示屏时设备型号不会发生变化。
请在运行时检查窗口指标或设备功能,以做出界面决策,而不是使用许可名单或检查 BUILD.MODEL 和内置显示大小。使用 Jetpack WindowManager API 或窗口大小类,针对各种屏幕尺寸和密度构建响应式和自适应布局。
支持外部外围设备
当用户把设备连接到外部显示屏时,通常会营造一个更像桌面设备的使用环境。这常常涉及使用外接键盘、鼠标、触控板、摄像头、麦克风和音箱。您需要确保应用能与这些外围设备无缝协作。这包括处理键盘快捷键、管理鼠标指针互动、正确支持外接摄像头或麦克风,以及遵循音频输出路由。如需了解详情,请参阅大屏幕上的输入兼容性。
提高用户工作效率
关联显示屏为提高用户工作效率提供了绝佳机会。现在,您可以使用相关工具来构建可提供与桌面应用相当的体验的移动应用。不妨考虑实现以下功能,以提高用户工作效率:
- 允许用户打开同一应用的多个实例。这对于比较文档、管理不同对话或同时查看多个文件等任务非常有用。
- 让用户能够通过拖放在应用内外共享丰富的数据。
- 通过实现强大的状态管理系统,帮助用户在配置更改后保持工作流程。
遵循这些指导原则并利用提供的代码示例,您可以创建能够无缝适应连接显示屏的应用,从而为用户提供更丰富、更高效的体验。