android開發中使用Activity實現Dialog很常見,
實現點擊窗體外關閉或者禁止Activity也很簡單:onCeate()中添加setFinishOnTouchOutside(true) 或者 setFinishOnTouchOutside(false) ;
但是問題來了,Activity如何監聽窗體外點擊事件呢?
我的思路是這樣的:
既然setFinishOnTouchOutside()可以控制點擊窗體外是否關閉Activity,那麼我們就進如這個方法:
Activity.java:
public void setFinishOnTouchOutside(boolean finish) {
mWindow.setCloseOnTouchOutside(finish);
}
繼續深入:
Window.java
public void setCloseOnTouchOutside(boolean close) {
mCloseOnTouchOutside = close;
mSetCloseOnTouchOutside = true;
}
看看mCloseOnTouchOutside在哪裏用:
Window.java
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
final boolean isOutside =
event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event)
|| event.getAction() == MotionEvent.ACTION_OUTSIDE;
if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
return true;
}
return false;
}
這個shouldCloseOnTouch()方法是最關鍵的方法,其中mCloseOnTouchOutside是開關,而後面的isOutSide是判斷是否點擊在窗體外面,滿足下面兩個條件任一就可以:
1、是DOWN事件 && isOutOfBounds();
private boolean isOutOfBounds(Context context, MotionEvent event) {
final int x = (int) event.getX();
final int y = (int) event.getY();
final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
final View decorView = getDecorView();
return (x < -slop) || (y < -slop)
|| (x > (decorView.getWidth()+slop))
|| (y > (decorView.getHeight()+slop));
}
不得不讚嘆一句:這個slop用的很漂亮,源碼就是嚴謹,一定學習。
2、是ACTION_OUTSIDE事件。
現在就有個疑惑,shouldCloseOnTouch(Context context, MotionEvent event)這個方法是哪裏調用的,並且傳進來MotionEvent。我們在Activity 裏一找,恍然大悟,就是在這裏finish()的,這下對上了。如下:
Activity.java
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
。。。
。。。
所以,我們的問題就解決了,看下面:
。。。
。。。
那我們就可以重寫Activity的onTouchEvent(),判斷只要滿足上面條件的任意一個就可以。
public class DialogActivity extends AppCompatActivity {
@Override
public boolean onTouchEvent(MotionEvent event) {
final boolean isOutside = event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(this, event);
if (isOutside) {
//TODO: 邏輯
finish();
return true;
}
return super.onTouchEvent(event);
}
}
private boolean isOutOfBounds(Context context, MotionEvent event) {
final int x = (int) event.getX();
final int y = (int) event.getY();
final int slop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
final View decorView = getWindow().getDecorView();
return (x < -slop) || (y < -slop)
|| (x > (decorView.getWidth()+slop))
|| (y > (decorView.getHeight()+slop));
}
。。。。。
。。。。。
但是,我們不能止步與此,上面還有ACTION_OUTSIDE 什麼情況下會滿足呢?
查詢資料後發現:ACTION_OUTSIDE 和 Window的這個Flag:
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH有關係,看WindowManager.java中的解釋:
/** Window flag: if you have set {@link #FLAG_NOT_TOUCH_MODAL}, you
* can set this flag to receive a single special MotionEvent with
* the action
* {@link MotionEvent#ACTION_OUTSIDE MotionEvent.ACTION_OUTSIDE} for
* touches that occur outside of your window. Note that you will not
* receive the full down/move/up gesture, only the location of the
* first down as an ACTION_OUTSIDE.
*/
public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
上面註釋的大致意思是:如果已經設置了FLAG_NOT_TOUCH_MODAL這個標誌位,再設置FLAG_WATCH_OUTSIDE_TOUCH這個標誌位後,就可以在點擊Window外時,收到一個特殊的action:ACTION_OUTSIDE,並且不在收到down/move/up事件。
。。。。。
。。。。。
下面可以實驗一下:
public class DialogActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
//窗口對齊屏幕寬度
Window win = this.getWindow();
WindowManager.LayoutParams lp = win.getAttributes();
lp.width = WindowManager.LayoutParams.MATCH_PARENT;
lp.height = (int) (getWindowManager().getDefaultDisplay().getHeight() * 0.5);
lp.gravity = Gravity.BOTTOM;//設置對話框置頂顯示
win.setAttributes(lp);
setContentView(R.layout.activity_main);
}
public boolean onTouchEvent(MotionEvent event) {
int action=event.getActionMasked();
switch (action){
case MotionEvent.ACTION_DOWN:
Log.e("tag", "MotionEvent.ACTION_DOWN");
break;
case MotionEvent.ACTION_OUTSIDE:
Log.e("tag", "MotionEvent.ACTION_OUTSIDE" );
}
return true ;
}
}
當點擊窗體外面時,打印MotionEvent.ACTION_OUTSIDE。
至此,對於題目問題,可得出另外一種解決方案。大家自己想想。
但我在項目中使用第二種方案後,發現一問題:稍等~