Android PopupWindow拦截物理返回

577 阅读1分钟

背景

最近有一需求,实现需要在popupWindow上拦截物理返回。起初在子View上做了keyEvent事件处理,但无法实现拦截。

查找原因

查看dimiss()的引用,发现popupWindow中的mDecorView对象对dispatchKeyEvent方法进行了重写,如下所示:

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    if (event.getKeyCode() == KeyEvent.KEYCODE_BACK
              || event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE) {
        if (getKeyDispatcherState() == null) {
            return super.dispatchKeyEvent(event);
        }

        if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
            final KeyEvent.DispatcherState state = getKeyDispatcherState();
            if (state != null) {
                state.startTracking(event, this);
            }
            return true;
        } else if (event.getAction() == KeyEvent.ACTION_UP) {
            final KeyEvent.DispatcherState state = getKeyDispatcherState();
            if (state != null && state.isTracking(event) && !event.isCanceled()) {
                //直接关闭
                dismiss();
                return true;
            }
        }
        return super.dispatchKeyEvent(event);
    } else {
        return super.dispatchKeyEvent(event);
    }
}

分析原因

代码并不难,具体分析这里就不做了。分析可知,按下返回键后,popupWindow会调用dismiss方法,导致子view无法处理keyEvent。

解决思路

起初打算使用反射,但mDecorView对象的所属类PopupDecorView是popupWindow的内部类,操作较为繁琐,遂决定重写dismiss方法,但如何确定调用dismiss的方法是dispatchKeyEvent呢?这里可以借助Throwable提供的栈帧信息进行判断。

示例如下:

class BackInterceptionPopupWindow(
    view: View,
    width: Int,
    height: Int,
    //返回Bool以判断是否执行dismiss()
    private val backInterception: () -> Boolean
) :
    PopupWindow(view, width, height) {


    override fun dismiss() {
        //获取堆栈中的信息
        val stackTrace = Throwable().stackTrace
        if (stackTrace.size >= 2 && stackTrace[1].methodName == "dispatchKeyEvent") {
            
            if (!backInterception()) {
                return
            }
        }
        super.dismiss()
    }
}

参考链接:# Android PopuWindow实现物理返回键监听