完美解决 PopupWindow 使其点击外部不消失
完美解决 Android 中 PopupWindow 使其点击外部不消失
首先贴下PopupWindow正常的创建方式:
public static HookPopupWindow createPopupWindow(Context context,int layoutIds,int width,int height, int style){
final View contentView = LayoutInflater.from(context).inflate(layoutIds, null, false);
HookPopupWindow mCountryWindow = new HookPopupWindow(contentView, width, height, true);
// 设置PopupWindow的背景
mCountryWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
// 设置PopupWindow是否能响应外部点击事件
mCountryWindow.setOutsideTouchable(true);
mCountryWindow.setTouchable(true);
// 设置PopupWindow是否能响应点击事件 两条一起使用才生效
if(style != 0)
mCountryWindow.setAnimationStyle(style);
return mCountryWindow;
}
这是我的工具类中的一个方法, HookPopupWindow 是一个继承 PopupWindow的子类, 大家就当一个PopupWindow来看即可, 我们都知道代码中 setOutsideTouchable
和 setTouchable
是为了让pw(即PopupWindow, 以下均简称pw), 可以点击内部以及使其点击外部消失, 有时我们为了达到点击pw外不使其消失, 将setOutsideTouchable
和 setTouchable
都设置为true, 但此时虽然pw不消失了, 但事件都传递到activity中了, 这肯定不是我们想要的. 看有的博客写在Activity中重写onDispatchEvent方法判断如果pw如果 showing, 直接return true将所有触摸事件拦截 , 但如果这种使pw不消失的需求多了在每个Activity岂不是很麻烦(或许可以放在基类里边, 但我偏偏不这么搞233), 但今天要说的不是这种方法, 我们可以继承PopupWindow在其子类中拦截dismiss()方法;
之前有继承pw并在dismiss()方法打断点, 发现当点击pw外部(前提是正常方式创建–见上边创建代码), 以及点击返回键都会走dismiss()方法, 这样就简单了, 我们继承pw重写dismiss()方法并注释掉super.dismiss()点击外部就可以实现不隐藏了, 代码如下:
public class HookPopupWindow extends PopupWindow {
public boolean canDismiss = true;// 设为false可以控制dismiss()无参方法不调用 主要是为了点击PopupWindow外部不消失
@Override
public void dismiss() {
// new Exception().printStackTrace();
if(canDismiss)
dismiss2();
else {
StackTraceElement[] stackTrace = new Exception().getStackTrace();
if(stackTrace.length >= 2 && "dispatchKeyEvent".equals(stackTrace[1].getMethodName())){
dismiss2();
}
}
}
public void dismiss2(){
super.dismiss();
}
}
在dismiss前将canDismiss
置为false
, 当我们需要dismiss的时候需要调用dismiss2()而不是dismiss(), 但是我在测试过程中遇到了一个问题, 就是当pw showing的时候, 我们点击返回键也是走到dismiss()方法,所以导致点back键不能dismiss, 并且Activity的onBackPrexxx
onKeyDown
onKeyUp
以及 popupWindow.getContentView().setOnKeyListener()
都无效, 这不是我想要的, 于是我在dismiss()方法中 new Exception().printStackTrace();
来打印方法堆栈, 看当点击返回键是由谁调过来的:
OK, 我们再来看PopupWindow 第2379 行
//View中的方法
public KeyEvent.DispatcherState getKeyDispatcherState() {
return mAttachInfo != null ? mAttachInfo.mKeyDispatchState : null;
}
多少有些偏差, 其实是在第 2382 行调用的(我看到是 api 26的代码), 最初看了这段代码之后想到通过反射将 getKeyDispatcherState
的返回值即 mAttachInfo.mKeyDispatchState
(该变量是final类型, 可以通过反射修改)通过反射置为null , 这样事件就可以传递下去, Activity就能收到 back事件了, 但实验结果表明事件并没有分发给Activity, 而是在某个地方被处理了, super.dispatchKeyEvent();
中一堆一堆的, 懒得看于是想着换个思路, 能不能不处理上述变量, 而是在dismiss()中进行拦截, 但是这样以来我怎么得知是用户点击的返回键而调用的 dismiss() 方法呢?增加一个变量? 也没地方加 看dispatchKeyEvent()方法中有没有保存修改什么变量? 貌似也没有, 正发愁的时候我看到 new Exception().printStackTrace();
刚才打的方法堆栈, 如果我能拿到堆栈字符串, 判断其中有dispatchKeyEvent
字符串不就说明是由用户点击返回键调用的吗? 于是就有了:
StackTraceElement[] stackTrace = new Exception().getStackTrace();
if(stackTrace.length >= 2 && "dispatchKeyEvent".equals(stackTrace[1].getMethodName())){
dismiss2();
}
实测可以完美解决PopupWindow点击外部使其不消失问题, 并且不影响返回键