背景
最近有一需求,实现需要在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()
}
}