返回顶部
首页 > 资讯 > 移动开发 >android PopupWindow点击外部和返回键消失的解决方法
  • 279
分享到

android PopupWindow点击外部和返回键消失的解决方法

方法popupwindowAndroid 2022-06-06 04:06:04 279人浏览 薄情痞子
摘要

刚接手PopupWindow的时候,我们都可能觉得很简单,因为它确实很简单,不过运气不好的可能就会踩到一个坑: 点击PopupWindow最外层布局以及点击返回键PopupWi

刚接手PopupWindow的时候,我们都可能觉得很简单,因为它确实很简单,不过运气不好的可能就会踩到一个坑:

点击PopupWindow最外层布局以及点击返回键PopupWindow不会消失

新手在遇到这个问题的时候可能会折腾半天,最后通过强大的网络找到一个解决方案,那就是跟PopupWindow设置一个背景

popupWindow.setBackgroundDrawable(drawable),这个drawable随便一个什么类型的都可以,只要不为空。

 Demo地址:SmartPopupWindow_jb51.rar

 下面从源码(我看的是Android-22)上看看到底发生了什么事情导致返回键不能消失弹出框:

先看看弹出框显示的时候代码showAsDropDown,里面有个preparePopup方法。


 public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
    if (isshowing() || mContentView == null) {
      return;
    }
    reGISterForScrollChanged(anchor, xoff, yoff, gravity);
    mIsShowing = true;
    mIsDropdown = true;
    WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
    preparePopup(p);
    updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity));
    if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
    if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
    p.windowAnimations = computeAnimationResource();
    invokePopup(p);
 }

再看preparePopup方法


  
  private void preparePopup(WindowManager.LayoutParams p) {
    if (mContentView == null || mContext == null || mWindowManager == null) {
      throw new IllegalStateException("You must specify a valid content view by "
          + "calling setContentView() before attempting to show the popup.");
    }
    if (mBackground != null) {
      final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
      int height = ViewGroup.LayoutParams.MATCH_PARENT;
      if (layoutParams != null &&
          layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
        height = ViewGroup.LayoutParams.WRAP_CONTENT;
      }
      // when a background is available, we embed the content view
      // within another view that owns the background drawable
      PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
      PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
          ViewGroup.LayoutParams.MATCH_PARENT, height
      );
      popupViewContainer.setBackground(mBackground);
      popupViewContainer.addView(mContentView, listParams);
      mPopupView = popupViewContainer;
    } else {
      mPopupView = mContentView;
    }
    mPopupView.setElevation(mElevation);
    mPopupViewInitialLayoutDirectionInherited =
        (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
    mPopupWidth = p.width;
    mPopupHeight = p.height;
  }

上面可以看到mBackground不为空的时候,会PopupViewContainer作为mContentView的Parent,下面看看PopupViewContainer到底干了什么


  private class PopupViewContainer extends FrameLayout {
    private static final String TAG = "PopupWindow.PopupViewContainer";
    public PopupViewContainer(Context context) {
      super(context);
    }
    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
      if (mAboveAnchor) {
        // 1 more needed for the above anchor state
        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
        View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);
        return drawableState;
      } else {
        return super.onCreateDrawableState(extraSpace);
      }
    }
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {  // 这个方法里面实现了返回键处理逻辑,会调用dismiss
      if (event.geTKEyCode() == KeyEvent.KEYCODE_BACK) {
        if (getKeyDispatcherState() == null) {
          return super.dispatchKeyEvent(event);
        }
        if (event.getAction() == KeyEvent.ACTION_DOWN
            && event.getRepeatCount() == 0) {
          KeyEvent.DispatcherState state = getKeyDispatcherState();
          if (state != null) {
            state.startTracking(event, this);
          }
          return true;
        } else if (event.getAction() == KeyEvent.ACTION_UP) {
          KeyEvent.DispatcherState state = getKeyDispatcherState();
          if (state != null && state.isTracking(event) && !event.isCanceled()) {
            dismiss();
            return true;
          }
        }
        return super.dispatchKeyEvent(event);
      } else {
        return super.dispatchKeyEvent(event);
      }
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
      if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
        return true;
      }
      return super.dispatchTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) { // 这个方法里面实现点击消失逻辑
      final int x = (int) event.getX();
      final int y = (int) event.getY();
      if ((event.getAction() == MotionEvent.ACTION_DOWN)
          && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
        dismiss();
        return true;
      } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
        dismiss();
        return true;
      } else {
        return super.onTouchEvent(event);
      }
    }
    @Override
    public void sendAccessibilityEvent(int eventType) {
      // clinets are interested in the content not the container, make it event source
      if (mContentView != null) {
        mContentView.sendAccessibilityEvent(eventType);
      } else {
        super.sendAccessibilityEvent(eventType);
      }
    }
  }

看到上面红色部分的标注可以看出,这个内部类里面封装了处理返回键退出和点击外部退出的逻辑,但是这个类对象的构造过程中(preparePopup方法中)却有个mBackground != null的条件才会创建

而mBackground对象在setBackgroundDrawable方法中被赋值,看到这里应该就明白一切了。


  
  public void setBackgroundDrawable(Drawable background) {
    mBackground = background;
    // 省略其他的
  }

setBackgroundDrawable方法除了被外部调用,构造方法中也会调用,默认是从系统资源中取的


  
  public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    mContext = context;
    mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
    final TypedArray a = context.obtainStyledAttributes(
        attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes);
    final Drawable bg = a.getDrawable(R.styleable.PopupWindow_popupBackground);
    mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0);
    mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false);
    final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1);
    mAnimationStyle = animStyle == R.style.Animation_PopupWindow ? -1 : animStyle;
    a.recycle();
    setBackgroundDrawable(bg);
  }

有些版本没有,android6.0版本preparePopup如下: 


  
  private void preparePopup(WindowManager.LayoutParams p) {
    if (mContentView == null || mContext == null || mWindowManager == null) {
      throw new IllegalStateException("You must specify a valid content view by "
          + "calling setContentView() before attempting to show the popup.");
    }
    // The old decor view may be transitioning out. Make sure it finishes
    // and cleans up before we try to create another one.
    if (mDecorView != null) {
      mDecorView.cancelTransitions();
    }
    // When a background is available, we embed the content view within
    // another view that owns the background drawable.
    if (mBackground != null) {
      mBackgroundView = createBackgroundView(mContentView);
      mBackgroundView.setBackground(mBackground);
    } else {
      mBackgroundView = mContentView;
    }
    mDecorView = createDecorView(mBackgroundView);
    // The background owner should be elevated so that it casts a shadow.
    mBackgroundView.setElevation(mElevation);
    // We may wrap that in another view, so we'll need to manually specify
    // the surface insets.
    final int surfaceInset = (int) Math.ceil(mBackgroundView.getZ() * 2);
    p.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
    p.hasManualSurfaceInsets = true;
    mPopupViewInitialLayoutDirectionInherited =
        (mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
    mPopupWidth = p.width;
    mPopupHeight = p.height;
  }

这里实现返回键监听的代码是mDecorView = createDecorView(mBackgroundView),这个并没有受到那个mBackground变量的控制,所以这个版本应该没有我们所描述的问题,感兴趣的可以自己去尝试一下

您可能感兴趣的文章:Android双击返回键退出程序的实现方法Android开发笔记之:返回键的复写onBackPressed()介绍Android中让按钮拥有返回键功能的方法及重写返回键功能Android返回键功能的实现方法Android 再按一次返回键退出程序实现思路Android中PopupWindow响应返回键并关闭的2种方法Android使alertDialog.builder不会点击外面和按返回键消失的方法Android 拦截返回键事件的实例详解


--结束END--

本文标题: android PopupWindow点击外部和返回键消失的解决方法

本文链接: https://lsjlt.com/news/22178.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作