前言
最近在看Android 多屏相关的问题,初步接触到了DisplayManager的一些东西。Android支持多个屏幕显示,手机的屏幕为主屏,通过HDMI,usb,wifi连接的屏幕为外显,还有一种是Android系统提供的Api即VirtualDisplay。
一,MediaRouter 或 DisplayManager
Android获取辅助屏幕(即外显)的方式有两种, MediaRouter 或 DisplayManager。Android 4.2的手机在开发中选项中,都有模拟辅助屏幕的功能,我们选择一个分辨率,打开它,模拟一个外部的屏幕。这个屏幕是开发者选项搞出来的,所以并不真实存在,模拟出来的(为了调试而用), 如果手机连接到一个辅助屏幕时,那个真实的屏幕才是手机的辅助屏幕。
1,MediaRouter
MediaRouter 用于和 MediaRouterService交互一起管理多媒体的播放行为,并维护当前已经配对上的remote display设备,包括WiFi diplay、蓝牙A2DP设备、Google Cast设备
Android媒体路由框架提供两种播放输出类型:远端播放和辅助输出。
远端播放类型 指的是辅助设备处理媒体内容的接收、解码和回放,而Android设备只起远程控制作用,如Google Cast。
辅助输出类型 则是应用本身处理媒体内容,包括媒体内容的引出和处理,并把处理结果直接呈现和串流到辅助接收设备上,辅助接收设备只是呈现媒体处理后的最终内容,如Android系统中使用该方式用来支持Wireless Display输出。这不是投屏吗?
媒体应用通过一个MediaRouter对象(媒体路由框架提供的一个对象)来使用媒体路由框架,用来选择媒体路由,并经过媒体路由框架的路由连接到选择的最终接收设备。
获得一个MediaRouter对象的方式为
MediaRouter mediaRouter = (MediaRouter) getSystemService(Context.MEDIA_ROUTER_SERVICE);
MediaRouter中常用的方法有以下:
RouterInfo是远端设备的信息类。
//获得RouterInfo的一个实例
MediaRouter.RouteInfo localRouteInfo =
mediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_AUDIO);
display = localRouteInfo != null ? localRouteInfo.getPresentationDisplay() : null;
if (display != null) {
showPresentation(display);
}
Presentation
presentation 是一种特殊的 dialog ,目的是为了在辅助屏幕上展示不同的内
presentation 是一个 dialog
根据生物遗传学的角度,presentation 无论被描述成什么天花乱坠的模样,它也是一个dialog。presentation 目的是显示在辅助屏幕上。
2,DisplayManager
DisplayManager displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
(1)获得当前存在的Displays
Display[] arrayOfDisplay = displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);//仅获取副屏
if (arrayOfDisplay.length > 0) { showPresentation(arrayOfDisplay[0]);//取第一个分屏使用 } 或者Display[] displays = displayManager.getDisplays();//获取系统所有的Display,包括桌面本身所在的Display
if (displays.length > 1) {
presentation2 = new MyPresentation2(getApplicationContext(), presentationDisplays[1]); presentation2.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); presentation2.show();
}
(2)创建一个虚屏VirtualDisplay,内含一个真实的Display对象
int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; //创建公共Display displayManager.createVirtualDisplay("displayName", width, height, densityDpi, mSurface, flags);
createVirtualDisplay()方法中有宽、高、屏幕密度
densityDpi:屏幕密度 ,一般选320,480,960等,值不同效果不同,使用的时候再去细看。我在项目中选用的480。
mSurface:创建VirtualDisplay要依赖的Surface,一种是从SurfaceView中获得, 一种是从ImageReader中获取。SurfaceView是view的子类,没有实现ViewGroup类,不能在SurfaceView上实现添加其他控件。SurfaceView提供直接访问一个可画图的界面,可以控制在界面顶部的子视图层。SurfaceView是提供给需要直接画像素而不是使用
窗体部件的应用使用的。Android图形系统中一个重要的概念和线索是surface。View及其子类(如TextView, Button)要画在surface上。每个surface创建一个Canvas对象(但属性时常改变),用来管理view在surface上的绘图 *** 作,如画点画线。
flags:是创建虚屏的一些标志位。
(3)VirtualDisplay展示内容
(a)展示一个Presentation
Presentation前面已说过, 本质是一个Dialog, 在VirtualDisplay中以Presentation为展示View的做法是自定义一个Presentation子类MainPresentation,setContentView()xml文件。然后创建一个Presentation实例
Presentation presentation = new TestPresentation(this, mDisplay.getDisplay());
presentation.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
presentation.show();
(b)展示一个Activity(如何将一个Activity启动到一个副屏上)
ActivityOptions options = null;
if (mDisplay != null) {
options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(mDisplay.getDisplay().getDisplayId());
}
Intent i = new Intent();
i.setClassName(getPackageName(), getPackageName()+ ".SecondActivity");
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
if (options != null) {
startActivity(i, options.toBundle());
}
值得注意的是,在副屏上展示一个Activity时,需要用显式启动的方式,启动的目标Activity在AndroidManifest.xml中声明属性android:exported = true.
二、遇到的点击事件的问题
我在Activity中定义了一个SurfaceView,创建好之后, 以SurfaceView的Surface为参,创建了一个VirtualDisplay,然后在VirtualDisplay上又展示了一个Presentation(内设了layout布局文件)。
public class VirtualDisplayActivity extends AppCompatActivity {
private SurfaceView mSurfaceView;
private DisplayManager mDisplayManager;
private SurfaceHolder mSurfaceHolder;
private mWidth;
private mHeight;
private SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback(){
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
mSurface = mSurfaceHolder.getSurface();
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
mWidth = width;
mHeight = height;
createVirtualAndShowPresentation();
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_vir);
mSurfaceView = findViewById(R.id.surface);
mSurfaceHolder = mSurfaceView.getHolder();
mDisplayManager = (DisplayManager) getSystemService(DISPLAY_SERVICE);
getSupportActionBar().setTitle("啦啦啦啦啦");
}
private void createVirtualAndShowPresentation() {
int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
mDisplay = mDisplayManager.createVirtualDisplay("maomao",mWidth, mHeight, 480, mSurface,flags);
Presentation presentation = new TestPresentation(this, mDisplay.getDisplay());
presentation.getWindow().
setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
presentation.show();
}
}
public class TestPresentation extends Presentation {
Button button;
int conut = 0;
public TestPresentation(Context outerContext, Display display) {
super(outerContext, display);
setContentView(R.layout.layout_presentation);
button = findViewById(R.id.clickBtn);
button.setOnClickListener(v -> {
button.setText(String.valueOf(conut));
conut++;
Log.e("maomao","我收到event了 click Presentation = " + conut);
});
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("maomao"," dispatchTouchEvent 我收到event了吗");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("maomao"," onTouchEvent 我收到event了吗");
return super.onTouchEvent(event);
}
}
最后运行效果为
那么问题来了。Presentation里的控件点击事件不响应。甚至VirtualDisplay整个界面都不响应touch事件,最外层能响应touch事件的是SurfaceView。重写SurfaceView的onTouchEvent(),能收到触摸事件,但Presentation死活都收不到触摸事件。
我做了以下实验,
(1)如果给SurfaceView设置背景色红色,那么Presentation的布局就不显示了。说明SurfaceView还是作为View在View层级里,显示在Presentation之上的(要知道Presentation是画在Surface上, 单独存在SurfacFlinger中)。
(2) 将VirtualDisplayActivity所在的window设成点击事件可穿透的,我的做法是
WindowManager.LayoutParams mLayoutParams = getWindow().getAttributes();
mLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
mLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
getWindow().setAttributes(mLayoutParams);
然后发现,SurfaceView收不到触摸事件,此时Presentation控件点击还是没反应。
(3) 我把Presentation显示在Activity所在的getWindow()所在的getDisplay(),发现能点击,完全没毛病。
于是我做了我的推理: SurfaceView是一个view子类。它接收到的事件到自身就不再分发了, 至于VirtualDisplay以及展示的Presentation都是画在它内部的Surface上的,怎么能接收到touch事件呢??所以SurfaceView里的Surface展示的VirtualDisplay内的页面是无法点击的!! 不知道我分析的对不对。看到的同学指教一下啊啊啊~
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------6 6月10号更新, 继续讨论SurfaceView + VirtualDisplay + Presentation/Activity 可点击问题
一、 结论: 无法穿透SurfaceView进行点击
以SurfaceView的Surface 为参创建VirtualDisplay, 这样VirtualDisplay里不管展示Presentation还是Activity 都无法点击, 因为Presentation还是Activity都是画在Surface里面的, 点击事件到达SurfaceView就已经是View的子类了,即到了View层级的最顶层。
二、 解决方法:
实现一个SurfaceView的子类,重写SurfaceView的onTouchEvent()方法,在SurfaceView接收到点击事件之后,原封不动的将event 手动分发到VirtualDisplay的Presentation和Activity内。
public class MainSurfaceView extends SurfaceView {
TouchEventDispatcher mDispatcher;
public MainSurfaceView(Context context) {
super(context);
}
public MainSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MainSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setTouchEventDispatcher(TouchEventDispatcher dispatcher) {
mDispatcher = dispatcher;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mDispatcher != null) {
mDispatcher.dispatchTouchEvent(event);
}
return super.onTouchEvent(event);
}
public interface TouchEventDispatcher {
void dispatchTouchEvent(MotionEvent event);
}
}
给MainSurfaceView设置TouchEventDispatcher 代码为
mSurfaceView.setTouchEventDispatcher(event -> {
if (mPresentation != null) {
mPresentation.dispatchTouchEventAgain(event);
}
});
TestPresentation里的dispatchTouchEventAgain()方法为
public void dispatchTouchEventAgain(MotionEvent event) {
getWindow().getDecorView().dispatchTouchEvent(event);
}
至此,SurfaceView的点击事件就被我们手动的传递到了VirtualDisplay内部的View层级中。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)