当前位置: 移动技术网 > IT编程>移动开发>Android > Android PopupWindow用法解析

Android PopupWindow用法解析

2019年07月24日  | 移动技术网IT编程  | 我要评论

拍婚纱照情侣装,广发银行信用卡积分礼品,孙政才的父亲

popupwindow使用
popupwindow这个类用来实现一个弹出框,可以使用任意布局的view作为其内容,这个弹出框是悬浮在当前activity之上的。 

popupwindow使用demo
这个类的使用,不再过多解释,直接上代码吧。
比如弹出框的布局: 

<?xml version="1.0" encoding="utf-8"?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:background="#ffbbffbb"
  android:orientation="vertical" >

  <textview
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp"
    android:text="hello my window"
    android:textsize="20sp" />

  <button
    android:id="@+id/button1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp"
    android:text="button"
    android:textsize="20sp" />

</linearlayout>

activity的布局中只有一个按钮,按下后会弹出框,activity代码如下: 

package com.example.hellopopupwindow;

import android.os.bundle;
import android.app.activity;
import android.content.context;
import android.util.log;
import android.view.layoutinflater;
import android.view.motionevent;
import android.view.view;
import android.view.view.onclicklistener;
import android.view.view.ontouchlistener;
import android.view.viewgroup.layoutparams;
import android.widget.button;
import android.widget.popupwindow;
import android.widget.toast;

public class mainactivity extends activity {

  private context mcontext = null;

  @override
  protected void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.activity_main);

    mcontext = this;

    button button = (button) findviewbyid(r.id.button);
    button.setonclicklistener(new view.onclicklistener() {

      @override
      public void onclick(view view) {

        showpopupwindow(view);
      }
    });
  }

  private void showpopupwindow(view view) {

    // 一个自定义的布局,作为显示的内容
    view contentview = layoutinflater.from(mcontext).inflate(
        r.layout.pop_window, null);
    // 设置按钮的点击事件
    button button = (button) contentview.findviewbyid(r.id.button1);
    button.setonclicklistener(new onclicklistener() {

      @override
      public void onclick(view v) {
        toast.maketext(mcontext, "button is pressed",
            toast.length_short).show();
      }
    });

    final popupwindow popupwindow = new popupwindow(contentview,
        layoutparams.wrap_content, layoutparams.wrap_content, true);

    popupwindow.settouchable(true);

    popupwindow.settouchinterceptor(new ontouchlistener() {

      @override
      public boolean ontouch(view v, motionevent event) {

        log.i("mengdd", "ontouch : ");

        return false;
        // 这里如果返回true的话,touch事件将被拦截
        // 拦截后 popupwindow的ontouchevent不被调用,这样点击外部区域无法dismiss
      }
    });

    // 如果不设置popupwindow的背景,无论是点击外部区域还是back键都无法dismiss弹框
    // 我觉得这里是api的一个bug
    popupwindow.setbackgrounddrawable(getresources().getdrawable(
        r.drawable.selectmenu_bg_downward));

    // 设置好参数之后再show
    popupwindow.showasdropdown(view);

  }

}

弹出框的布局中有一个textview和一个button,button点击后显示toast,如图: 

第一次实现的时候遇到了问题,就是弹出框不会在按下back键的时候消失,点击弹框外区域也没有正常消失,搜索了一下,都说只要设置背景就好了。
然后我就找了个图片,果然弹框能正常dismiss了(见注释)。

popupwindow源码分析
为了解答一下上面的问题,看看源码(最新api level 19,android 4.4.2)。
1.显示方法
显示提供了两种形式:
showatlocation()显示在指定位置,有两个方法重载:

public void showatlocation(view parent, int gravity, int x, int y)

public void showatlocation(ibinder token, int gravity, int x, int y) 

showasdropdown()显示在一个参照物view的周围,有三个方法重载:

public void showasdropdown(view anchor)

public void showasdropdown(view anchor, int xoff, int yoff)

public void showasdropdown(view anchor, int xoff, int yoff, int gravity) 

最后一种带gravity参数的方法是api 19新引入的。
弹出的方法中首先需要preparepopup() ,最后再invokepopup()
prepare的方法中可以看到有无背景的分别: 

  /**
   * <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 mnodified 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.setbackgrounddrawable(mbackground);
      popupviewcontainer.addview(mcontentview, listparams);

      mpopupview = popupviewcontainer;
    } else {
      mpopupview = mcontentview;
    }
    mpopupviewinitiallayoutdirectioninherited =
        (mpopupview.getrawlayoutdirection() == view.layout_direction_inherit);
    mpopupwidth = p.width;
    mpopupheight = p.height;
  }

2.背景是否为空对touch事件的影响
如果有背景,则会在contentview外面包一层popupviewcontainer之后作为mpopupview,如果没有背景,则直接用contentview作为mpopupview。
而这个popupviewcontainer是一个内部私有类,它继承了framelayout,在其中重写了key和touch事件的分发处理: 

 @override
    public boolean dispatchkeyevent(keyevent event) {
      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);
      }
    }

由于popupview本身并没有重写key和touch事件的处理,所以如果没有包这个外层容器类,点击back键或者外部区域是不会导致弹框消失的。

补充case: 弹窗不消失,但是事件向下传递
如上所述:
设置了popupwindow的background,点击back键或者点击弹窗的外部区域,弹窗就会dismiss.
相反,如果不设置popupwindow的background,那么点击back键和点击弹窗的外部区域,弹窗是不会消失的.
那么,如果我想要一个效果,点击外部区域,弹窗不消失,但是点击事件会向下面的activity传递,比如下面是一个webview,我想点击里面的链接等.  

研究了半天,说是要给window设置一个flag,windowmanager.layoutparams.flag_not_touch_modal
看了源码,这个flag的设置与否是由一个叫mnottouchmodal的字段控制,但是设置该字段的set方法被标记为@hide。
所以要通过反射的方法调用: 

   /**
   * set whether this window is touch modal or if outside touches will be sent
   * to
   * other windows behind it.
   *
   */
  public static void setpopupwindowtouchmodal(popupwindow popupwindow,
      boolean touchmodal) {
    if (null == popupwindow) {
      return;
    }
    method method;
    try {

      method = popupwindow.class.getdeclaredmethod("settouchmodal",
          boolean.class);
      method.setaccessible(true);
      method.invoke(popupwindow, touchmodal);

    }
    catch (exception e) {
      e.printstacktrace();
    }

  }

然后在程序中:  
uiutils.setpopupwindowtouchmodal(popupwindow, false);

该popupwindow外部的事件就可以传递给下面的activity了。

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网