這個(gè)問題很簡(jiǎn)單,這個(gè)是因?yàn)镈ialog是Focusable的。如果不是Focusable的,那么Dialog 彈出后 Activity 還是可以響應(yīng)用戶事件的。
首先要明確一個(gè)概念是窗口是事件分發(fā)的基本單位,而不是activity或者應(yīng)用。系統(tǒng)的事件總是要先分給窗口,決定要分給哪個(gè)窗口是在系統(tǒng)的InputDispatcher.cpp這個(gè)類中。
對(duì)于touch事件,還要再由窗口交給DecorView在控件樹中進(jìn)行分發(fā),最終到達(dá)某個(gè)View。但在此之前,會(huì)交由activity進(jìn)行處理一下。
可以用以下demo測(cè)試一下:
view = new MyButton(this);
getWindowManager().addView(view, getWindowParams());
private WindowManager.LayoutParams getWindowParams(){
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.format = PixelFormat.TRANSLUCENT;// 支持透明
/*這種接收時(shí)間的范圍大小是整個(gè)屏幕*/
// params.flags |= WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;// 接受窗口外事件
/*這個(gè)接收事件的范圍是自己設(shè)置的大小*/
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其實(shí)是添加一個(gè)子窗口。添加完MyDialog以后,我們的activity一共就有兩個(gè)窗口了,另外一個(gè)是默認(rèn)生成的子窗口。這一點(diǎn)可以通過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。子窗口的名字如果不調(diào)用params.setTitle()進(jìn)行設(shè)置,那么默認(rèn)也是所屬activity的ComponentName,只是前面的地址不一樣而已。
可以看到,子窗口是在主窗口上面,即子窗口的z序是小于主窗口的。
我們的MyDialog 沒有加上params.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 這句話的時(shí)候,子窗口的touch區(qū)域其實(shí)是整個(gè)Frame,也就是主窗口完完全全被子窗口擋住了,自然無法響應(yīng)用戶事件。這個(gè)說法有什么證據(jù)嗎?可以通過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]卻是全屏的,和主窗口一樣大,又在主窗口上面,于是擋住了主窗口;所以主窗口,即大家經(jīng)常說的acitivity,無法接收到touch事件。只有當(dāng)子窗口接受到事件dismiss以后,才能接收到事件。
當(dāng)MyDialog 加上params.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 這句話的時(shí)候 ,它的touch區(qū)域發(fā)生了變化。
6: name='Window{e3f21ef u0 MyDialog}', paused=false, hasFocus=false visible=true, canReceiveKeys=false frame=[590,1127][1080,1287], **touchableRegion=[590,1127][1080,1287]**,
現(xiàn)在子窗口看起來多大,能接受touch事件的區(qū)域也就多大了,其余區(qū)域不會(huì)在阻攔主窗口的事件。而且現(xiàn)在子窗口無法接受key事件,canReceiveKeys=false。
最后,為什么谷歌要這么做?原因不是很清楚,可以猜測(cè)一下:如果一個(gè)窗口是Focusable的,那么其是要響應(yīng)全局事件的,因?yàn)镕ocusable對(duì)應(yīng)著key事件,意味可以接受key事件,而key事件是全局的(沒有一個(gè)具體的區(qū)域,touch事件是有局域的,就是touchableRegion),所以為了統(tǒng)一,也把touchableRegion改成了全屏。如果你調(diào)反過來,如果一個(gè)子窗口是不能接受key事件的,那么它實(shí)際上多大,它的touchableRegion也就是多大。
總結(jié):
因?yàn)锳ndroid中特意淡化了Window的概念,所以可能很多同學(xué)只知道activity而不知道Window。如果弄清楚了Window在事件分發(fā)中的作用,這個(gè)問題還是容易理解的。
結(jié)論就是子窗口彈出了以后,因?yàn)樗诖翱诘纳厦?,而且他的touchableRegion是全屏,擋住了主窗口,所以系統(tǒng)把touch事件都發(fā)給子窗口了,主窗口無法響應(yīng)。