Activity实现Dialog如何监听窗体外点击事件

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。
至此,对于题目问题,可得出另外一种解决方案。大家自己想想。

但我在项目中使用第二种方案后,发现一问题:稍等~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章