Android PopupWindow用法解析
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了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。