欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2023-12-15 08:25:16
刚接手popupwindow的时候,我们都可能觉得很简单,因为它确实很简单,不过运气不好的可能就会踩到一个坑: 点击popupwindow最外层布局以及点击返回键popu...

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

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

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

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

 demo地址:smartpopupwindow_jb51.rar

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

 下面从源码(我看的是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方法

  /**
   * <p>prepare the popup by embedding in into a new viewgroup if the
   * background drawable is not null. if embedding is required, the layout
   * parameters' height is modified to take into account the background's
   * padding.</p>
   *
   * @param p the layout parameters of the popup's content view
   */
  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方法中被赋值,看到这里应该就明白一切了。

  /**
   * specifies the background drawable for this popup window. the background
   * can be set to {@code null}.
   *
   * @param background the popup's background
   * @see #getbackground()
   * @attr ref android.r.styleable#popupwindow_popupbackground
   */
  public void setbackgrounddrawable(drawable background) {
    mbackground = background;
    // 省略其他的
  }

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

  /**
   * <p>create a new, empty, non focusable popup window of dimension (0,0).</p>
   * 
   * <p>the popup does not provide a background.</p>
   */
  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如下: 

  /**
   * prepare the popup by embedding it into a new viewgroup if the background
   * drawable is not null. if embedding is required, the layout parameters'
   * height is modified to take into account the background's padding.
   *
   * @param p the layout parameters of the popup's content view
   */
  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变量的控制,所以这个版本应该没有我们所描述的问题,感兴趣的可以自己去尝试一下

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

上一篇:

下一篇: