Android实现双屏异显——完整项目详解与代码实现
一、项目介绍
1.1 什么是双屏异显
双屏异显,顾名思义,是指在一台Android设备连接两个屏幕(如主屏和副屏)时,主屏与副屏可以显示不同的内容,而不是镜像显示。此功能广泛应用于如下场景:
-
商业广告机:主屏为操控界面,副屏为广告显示;
-
车载系统:主屏控制导航、娱乐,副屏用于乘客互动;
-
会议展示:主屏用于演讲控制,副屏用于投屏展示;
-
POS机系统:主屏用于商家操作,副屏展示支付二维码等内容。
1.2 项目目标
本项目旨在实现以下目标:
-
利用 Android 的
Presentation
类实现双屏异显; -
实现主副屏独立加载不同的布局和功能;
-
支持动态识别副屏插拔;
-
提供完整的可运行源码,方便学习与拓展。
二、实现双屏异显所需的相关技术与知识
2.1 Android 多屏显示原理
Android 中支持多显示屏,底层通过 DisplayManager
管理显示设备。多个显示设备可以通过 HDMI、USB、WiFi(无线投屏)等方式连接到 Android 设备上。
2.2 Presentation 类介绍
Presentation
是 Android 提供的用于在不同显示屏上显示内容的类。它继承自 Dialog
,本质上是一个运行在另一个 Display
上的窗口。
-
每个
Display
都有自己的Context
; -
Presentation
可以使用独立布局; -
可响应生命周期事件:
onCreate()
、onDismiss()
等。
2.3 显示设备监听
-
使用
DisplayManager.DisplayListener
监听副屏连接与断开; -
判断副屏是否可用;
-
在副屏可用时启动
Presentation
。
三、项目实现思路
3.1 项目结构说明
-
MainActivity
: 主屏界面; -
SecondScreenPresentation
: 副屏显示界面; -
通过
DisplayManager
获取外接屏幕信息; -
动态创建并显示
Presentation
实例; -
实现屏幕插拔监听与自动处理。
四、项目完整代码
// ==================== MainActivity.java ====================
package com.example.dualscreendemo;
import android.app.Activity;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private DisplayManager mDisplayManager;
private SecondScreenPresentation mPresentation;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // 主屏布局
// 初始化 DisplayManager
mDisplayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
// 获取所有连接的显示设备(包括内置和外接)
Display[] displays = mDisplayManager.getDisplays();
for (Display display : displays) {
// 忽略默认屏幕(Display.DEFAULT_DISPLAY = 0)
if (display.getDisplayId() != Display.DEFAULT_DISPLAY) {
showPresentation(display);
break;
}
}
// 注册屏幕监听器,监控外接屏幕插拔
mDisplayManager.registerDisplayListener(new DisplayManager.DisplayListener() {
@Override
public void onDisplayAdded(int displayId) {
Log.d(TAG, "外接显示器连接: " + displayId);
Display display = mDisplayManager.getDisplay(displayId);
showPresentation(display);
}
@Override
public void onDisplayRemoved(int displayId) {
Log.d(TAG, "外接显示器断开: " + displayId);
if (mPresentation != null && mPresentation.getDisplay().getDisplayId() == displayId) {
mPresentation.dismiss();
mPresentation = null;
}
}
@Override
public void onDisplayChanged(int displayId) {
Log.d(TAG, "显示器状态变化: " + displayId);
}
}, null);
// 主屏按钮控制
Button btn = findViewById(R.id.btn_change_text);
btn.setOnClickListener(v -> {
if (mPresentation != null) {
mPresentation.updateText("副屏内容已更新!");
}
});
}
// 显示副屏内容
private void showPresentation(Display display) {
if (mPresentation != null) {
mPresentation.dismiss();
}
mPresentation = new SecondScreenPresentation(this, display);
mPresentation.show();
}
}
// ==================== SecondScreenPresentation.java ====================
package com.example.dualscreendemo;
import android.app.Presentation;
import android.content.Context;
import android.os.Bundle;
import android.view.Display;
import android.view.Window;
import android.widget.TextView;
public class SecondScreenPresentation extends Presentation {
private TextView tv;
public SecondScreenPresentation(Context outerContext, Display display) {
super(outerContext, display);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE); // 去标题栏
setContentView(R.layout.presentation_second); // 副屏布局
tv = findViewById(R.id.tv_second_screen);
}
// 提供更新副屏内容的方法
public void updateText(String content) {
if (tv != null) {
tv.setText(content);
}
}
}
// ==================== activity_main.xml (主屏布局) ====================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:background="#ffffff">
<TextView
android:id="@+id/tv_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="主屏显示"
android:textSize="24sp"
android:textColor="#000000" />
<Button
android:id="@+id/btn_change_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="修改副屏内容" />
</LinearLayout>
<!-- ==================== presentation_second.xml (副屏布局) ==================== -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/second_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:background="#ffcc00">
<TextView
android:id="@+id/tv_second_screen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="副屏内容"
android:textSize="28sp"
android:textColor="#000000" />
</LinearLayout>
五、代码解读
-
onCreate()
:主Activity生命周期方法,初始化 UI,检查副屏是否存在。 -
showPresentation()
:创建并显示Presentation
实例到副屏。 -
updateText()
:用于从主屏控制副屏文字更新。 -
DisplayManager.registerDisplayListener()
:监听副屏插拔状态。 -
SecondScreenPresentation.onCreate()
:副屏界面初始化。
六、项目总结与拓展
6.1 实现效果回顾
-
成功实现在副屏显示独立内容;
-
主屏可以动态控制副屏内容;
-
支持副屏插拔检测;
-
代码结构清晰,方便拓展。
6.2 实践中的注意事项
-
部分设备不支持双屏异显(需硬件支持);
-
HDMI 插拔可能导致 Activity 重启,需处理状态恢复;
-
多副屏支持需遍历所有非主屏 Display。
6.3 可扩展方向
-
多媒体播放:副屏播放视频、广告;
-
触摸互动:副屏支持触摸输入(如USB触控屏);
-
多副屏支持:支持两个以上的 Display;
-
数据同步:主副屏之间通过 Broadcast/EventBus 通信;
-
使用 Jetpack Compose 版本实现:提高现代化体验;