29、车载多屏交互:CarMultiDisplayManager、屏幕投射、跨屏拖拽、应用流转
多屏交互,是车载系统里最“炫”但也最“坑”的功能之一。
你想想看,一辆车里可能有三块屏:仪表盘、中控、副驾娱乐屏。甚至后排还有两块。这些屏幕怎么协同工作?怎么让用户觉得“嗯,这车机真聪明”,而不是“这什么鬼,拖都拖不动”?
我最早接触多屏是在一个高端车型项目里。客户要求副驾看视频,中控导航,仪表显示车速。听起来简单,但真正做起来,你会发现——屏幕之间的“沟通”远比想象中复杂。
29.1 CarMultiDisplayManager:多屏管理的“大脑”
Android Automotive OS 里,多屏管理的核心就是 CarMultiDisplayManager。它负责注册、查询、分配每个屏幕的显示区域和逻辑。
说白了,它就像个“屏幕管家”。每个屏幕都有一个唯一的 DisplayId,系统通过这个 ID 来区分它们。
核心概念:
- DisplayId:每个物理屏幕的唯一标识。仪表盘通常是 DisplayId=0,中控是 DisplayId=1,副驾是 DisplayId=2。
- ZoneId:逻辑区域。一个物理屏幕可以切分成多个 Zone,比如中控屏左边是导航,右边是音乐。
- WindowManager:负责把应用窗口分配到对应的 Display 上。
我习惯在系统启动时,先通过 CarMultiDisplayManager 获取所有可用屏幕的列表。这样可以避免后续操作时找不到目标屏幕。
// 获取 CarMultiDisplayManager 实例
CarMultiDisplayManager displayManager =
(CarMultiDisplayManager) car.getCarManager(Car.CAR_MULTI_DISPLAY_SERVICE);
// 获取所有屏幕信息
List<DisplayInfo> displays = displayManager.getDisplayInfos();
for (DisplayInfo info : displays) {
Log.d("MultiDisplay", "Display ID: " + info.getDisplayId()
+ ", Type: " + info.getType());
}
这里有个坑:不要假设屏幕的顺序。我曾经在一个项目里,默认 DisplayId=0 是仪表盘,结果某次 OTA 升级后,系统把中控屏排到了 0 号位。嗯,那场面……所有导航信息都跑到了仪表盘上。后来我改用 getDisplayType() 来判断屏幕角色,再也没出过问题。
29.2 屏幕投射:把内容“扔”到另一块屏上
屏幕投射,就是让一个应用的内容显示在另一块屏幕上。最常见的场景:副驾在看视频,突然想投到中控屏上。
实现方式有两种:
- 基于 DisplayManager 的投射:把整个 Activity 或 View 迁移到目标 Display。
- 基于 MediaProjection 的投射:捕获屏幕内容,实时传输到另一块屏。
我个人更推荐第一种,因为它更稳定,而且系统原生支持。
// 把当前 Activity 投射到副驾屏
DisplayManager displayManager =
(DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
Display[] displays = displayManager.getDisplays();
Display targetDisplay = null;
for (Display display : displays) {
if (display.getDisplayId() == 2) { // 副驾屏
targetDisplay = display;
break;
}
}
if (targetDisplay != null) {
// 创建 Presentation
Presentation presentation = new Presentation(this, targetDisplay);
presentation.setContentView(R.layout.layout_video);
presentation.show();
}
提示:Presentation 的生命周期和宿主 Activity 是绑定的。如果 Activity 被销毁,Presentation 也会消失。记得在 onPause 里处理一下。
我在项目中遇到过一个问题:投射后,副驾屏的触摸事件无法传递到主屏的应用。后来发现是 InputManager 的焦点分配没处理好。解决方案是手动设置 View.setFocusable(true),并注册触摸监听。
29.3 跨屏拖拽:从一块屏拖到另一块屏
跨屏拖拽,听起来很酷,做起来很烦。
用户想从副驾屏拖一张图片到中控屏的导航里。这涉及到两个屏幕之间的数据传递和坐标映射。
核心流程:
- 起点:在源屏幕启动 Drag and Drop,把数据封装到
ClipData里。 - 传输:通过
DragShadowBuilder显示拖拽阴影。 - 终点:目标屏幕的 View 接收
DragEvent,解析数据。
但这里有个关键问题:坐标转换。两块屏的分辨率、DPI 可能完全不同。你拖拽的阴影在源屏上位置正确,到了目标屏可能就偏了。
// 在源屏幕启动拖拽
View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view);
ClipData data = ClipData.newPlainText("label", "Hello from other screen");
view.startDragAndDrop(data, shadowBuilder, null, 0);
// 在目标屏幕接收拖拽
targetView.setOnDragListener((v, event) -> {
switch (event.getAction()) {
case DragEvent.ACTION_DROP:
ClipData.Item item = event.getClipData().getItemAt(0);
String text = item.getText().toString();
// 处理接收到的数据
break;
}
return true;
});
警告:跨屏拖拽时,DragEvent 的坐标是相对于源屏幕的。你需要手动转换成目标屏幕的坐标。我曾经因为忘了做这个转换,导致用户拖拽时,阴影在目标屏上乱跳。解决方案是获取两个屏幕的 DisplayMetrics,然后做比例换算。
29.4 应用流转:让应用“跟着用户走”
应用流转,是比屏幕投射更高级的玩法。用户从副驾屏切换到中控屏,应用的状态、数据、UI 都无缝迁移过去。
这背后依赖的是 CarMultiDisplayManager 的 moveTaskToDisplay() 方法。
// 把当前应用的任务迁移到目标屏幕
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
int taskId = getTaskId();
am.moveTaskToDisplay(taskId, targetDisplayId);
但光迁移任务还不够。应用的状态怎么保存?
我建议使用 SavedStateHandle 或者 ViewModel 来保存关键数据。这样在屏幕切换时,应用可以恢复到之前的状态。
举个例子:副驾屏上正在播放视频,进度条在 30 分钟。流转到中控屏后,视频应该从 30 分钟继续播放,而不是从头开始。
// 保存播放进度
public class VideoViewModel extends ViewModel {
private final SavedStateHandle savedStateHandle;
public VideoViewModel(SavedStateHandle savedStateHandle) {
this.savedStateHandle = savedStateHandle;
}
public void saveProgress(long progress) {
savedStateHandle.set("progress", progress);
}
public long getProgress() {
return savedStateHandle.get("progress");
}
}
提示:应用流转时,onConfigurationChanged() 会被触发。记得在这里重新绑定 UI 和数据。我习惯在 ViewModel 里统一管理状态,Activity 只负责展示。
29.5 知识体系总览
下面这张图,是我梳理的多屏交互核心逻辑。你看一眼,基本就能明白整个流程了。
这张图里,CarMultiDisplayManager 是总入口。它向下分出三个核心能力:屏幕投射、跨屏拖拽、应用流转。每个能力都有对应的实现方式和注意事项。
嗯,这里要注意:这三个能力不是互斥的。你可以同时使用。比如,先投射一个视频到副驾屏,然后拖拽一个文件到中控屏,最后把整个应用流转到后排屏。只要你的架构设计得够灵活,这些都能实现。
我个人觉得,多屏交互最难的不是技术实现,而是用户体验设计。用户不会关心你用了什么 API,他们只关心“拖过去顺不顺”、“切换快不快”。所以,在开发过程中,多和 UI/UX 同学沟通,多做真机测试。
我曾经在一个项目里,因为忽略了屏幕刷新率的差异,导致拖拽动画在副驾屏上卡顿。后来发现副驾屏是 30Hz,中控屏是 60Hz。解决方案是在拖拽时,根据目标屏幕的刷新率动态调整动画帧率。
好了,多屏交互的内容就这些。记住:屏幕是死的,逻辑是活的。只要把 CarMultiDisplayManager 吃透,剩下的就是发挥你的想象力了。