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

Android PopupWindow用法解析

程序员文章站 2024-03-04 13:09:05
popupwindow使用 popupwindow这个类用来实现一个弹出框,可以使用任意布局的view作为其内容,这个弹出框是悬浮在当前activity之上的。 ...

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了。

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