这个问题很简单,这个是因为Dialog是Focusable的。如果不是Focusable的,那么Dialog 弹出后 Activity 还是可以响应用户事件的。
首先要明确一个概念是窗口是事件分发的基本单位,而不是activity或者应用。系统的事件总是要先分给窗口,决定要分给哪个窗口是在系统的InputDispatcher.cpp这个类中。
对于touch事件,还要再由窗口交给DecorView在控件树中进行分发,最终到达某个View。但在此之前,会交由activity进行处理一下。
可以用以下demo测试一下:
view = new MyButton(this);
getWindowManager().addView(view, getWindowParams());
private WindowManager.LayoutParams getWindowParams(){
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.format = PixelFormat.TRANSLUCENT;// 支持透明
/*这种接收时间的范围大小是整个屏幕*/
// params.flags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;// 接受窗口外事件
/*这个接收事件的范围是自己设置的大小*/
params.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.width = 490;//窗口的宽和高
params.height = 160;
params.x = 0;//窗口位置的偏移量
params.y = 50;
params.setTitle("MyDialog");
params.gravity = Gravity.START|Gravity.TOP;
return params;
}
public void dismiss(){
getWindowManager().removeView(view);
}
class MyButton extends AppCompatButton {
private TestWmsActivity activity;
public MyButton(TestWmsActivity context) {
super(context);
activity = context;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(Config.TAG, "onTouchEvent: " + event.getAction());
activity.dismiss();
return true;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
Log.d(Config.TAG, "onKeyDown: " + keyCode);
activity.dismiss();
return true;
}
}
getWindowManager().addView 这里的添加view其实是添加一个子窗口。添加完MyDialog以后,我们的activity一共就有两个窗口了,另外一个是默认生成的子窗口。这一点可以通过adb shell dumpsys window visible来查看:
Window #6 Window{57cc6f1 u0 MyDialog}:
...
Window #7 Window{a38a957 u0 com.example.xcm.demo/com.example.xcm.demo.wms.TestWmsActivity}:
...
主窗口的名字一般就是activity的ComponentName。子窗口的名字如果不调用params.setTitle()进行设置,那么默认也是所属activity的ComponentName,只是前面的地址不一样而已。
可以看到,子窗口是在主窗口上面,即子窗口的z序是小于主窗口的。
我们的MyDialog 没有加上params.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 这句话的时候,子窗口的touch区域其实是整个Frame,也就是主窗口完完全全被子窗口挡住了,自然无法响应用户事件。这个说法有什么证据吗?可以通过adb shell dumpsys input 来查看一下:
在Input Dispatcher State:一栏中,有以下信息:
6: name='Window{e3f21ef u0 MyDialog}', paused=false, hasFocus=true visible=true, canReceiveKeys=true frame=[590,1127][1080,1287], **touchableRegion=[0,0][1080,2340]**,
7: name='Window{d68c785 u0 com.example.xcm.demo/com.example.xcm.demo.wms.TestWmsActivity}', paused=false, hasFocus=false, visible=true, canReceiveKeys=true, frame=[0,0][1080,2340], **touchableRegion=[0,0][1080,2340]**
可以看到虽然子窗口的看起来只有 frame=[590,1127][1080,1287] 这么大,但其touchableRegion=[0,0][1080,2340]却是全屏的,和主窗口一样大,又在主窗口上面,于是挡住了主窗口;所以主窗口,即大家经常说的acitivity,无法接收到touch事件。只有当子窗口接受到事件dismiss以后,才能接收到事件。
当MyDialog 加上params.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 这句话的时候 ,它的touch区域发生了变化。
6: name='Window{e3f21ef u0 MyDialog}', paused=false, hasFocus=false visible=true, canReceiveKeys=false frame=[590,1127][1080,1287], **touchableRegion=[590,1127][1080,1287]**,
现在子窗口看起来多大,能接受touch事件的区域也就多大了,其余区域不会在阻拦主窗口的事件。而且现在子窗口无法接受key事件,canReceiveKeys=false。
最后,为什么谷歌要这么做?原因不是很清楚,可以猜测一下:如果一个窗口是Focusable的,那么其是要响应全局事件的,因为Focusable对应着key事件,意味可以接受key事件,而key事件是全局的(没有一个具体的区域,touch事件是有局域的,就是touchableRegion),所以为了统一,也把touchableRegion改成了全屏。如果你调反过来,如果一个子窗口是不能接受key事件的,那么它实际上多大,它的touchableRegion也就是多大。
总结:
因为Android中特意淡化了Window的概念,所以可能很多同学只知道activity而不知道Window。如果弄清楚了Window在事件分发中的作用,这个问题还是容易理解的。
结论就是子窗口弹出了以后,因为它在窗口的上面,而且他的touchableRegion是全屏,挡住了主窗口,所以系统把touch事件都发给子窗口了,主窗口无法响应。