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。
至此,對於題目問題,可得出另外一種解決方案。大家自己想想。

但我在項目中使用第二種方案後,發現一問題:稍等~

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