Android VirtualDisplay的用法和遇到的点击事件问题

Android VirtualDisplay的用法和遇到的点击事件问题,第1张

前言

     最近在看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层级中。 

                      

 

 

 

 

 

 

 

 

 

 

 

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/992625.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存