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。
至此,对于题目问题,可得出另外一种解决方案。大家自己想想。
但我在项目中使用第二种方案后,发现一问题:稍等~