解决 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来看即可, 我们都知道代码中 setOutsideTouchablesetTouchable 是为了让pw(即PopupWindow, 以下均简称pw), 可以点击内部以及使其点击外部消失, 有时我们为了达到点击pw外不使其消失, 将setOutsideTouchablesetTouchable都设置为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 行

![这里写图片描述](https://img-blog.csdn.net/20180803193825453?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI3MDcwMTE3/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
 //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点击外部使其不消失问题, 并且不影响返回键

猜你喜欢

转载自blog.csdn.net/qq_27070117/article/details/81393512