浅谈PopupWindow在Android开发中的使用

在Android中弹出式菜单(以下称弹窗)是使用十分广泛一种菜单呈现的方式,弹窗为用户交互提供了便利。关于弹窗的实现大致有以下两种方式AlertDialog和PopupWindow,当然网上也有使用Activity并配合Dialog主题的方式实现弹窗,有兴趣的朋友也可以去研究一下。对于AlertDialog和PopupWindow两者的最主要区别也有以下两点:

1 、位置是否固定。 AlertDialog在位置显示上是固定的,而PopupWindow则相对比较随意,能够在主屏幕上的任意位置显示。

2、是否会阻塞UI线程。 AlertDialog在显示的时候不会阻塞UI线程,而PopupWindow在显示的时候会阻塞UI线程。

PopupWindow在android.widget包下,Google官方文档对PopupWindow的描述是:

"A popup window that can be used to display an arbitrary view. The popupwindow is a floating container that appears on top of the current activity."

也就是说PopupWindow是一个以弹窗方式呈现的控件,可以用来显示任意视图(View),而且会浮动在当前活动(activity)的顶部”。因此我们可以通过PopupWindow实现各种各样的弹窗效果,进行信息的展示或者是UI交互,由于PopupWindow自定义布局比较方便,而且在显示位置比较自由不受限制,因此受到众多开发者的青睐。

废话不多说,进入正题。


PopupWindow的使用


其实PopupWindow的使用非常简单,总的来说分为两步:

1、调用PopupWindow的构造器创建PopupWindow对象,并完成一些初始化设置。

2、调用PopupWindow的showAsDropDown(View view)将PopupWindow作为View组件的下拉组件显示出来;或调用PopupWindow的showAtLocation()方法将PopupWindow在指定位置显示出来。

创建并完成初始化设置:

PopupWindow popupWindow =new PopupWindow(this);

popupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);

popupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);

popupWindow.setContentView(LayoutInflater.from(this).inflate(R.layout.layout_popupwindow_style01,null));

popupWindow.setBackgroundDrawable(new ColorDrawable(0x00000000));

popupWindow.setOutsideTouchable(false);

popupWindow.setFocusable(true);


其中,setWidth、setHeight和setContentView三者必须实现,否则将不会显示任何视图。

 setwidth和setHeight的参数值可以是具体的数值,也可以是MATCH_PARENT或者是WRAP_CONTENT。

setContentView则是为PopupWindow设置视图内容。

  setFocusable顾名思义就是让PopupWindow获得焦点。

 setBackgroundDrawable从字面理解就是为PopupWindow设置一个背景。 

setOutsideTouchable则表示PopupWindow内容区域外的区域是否响应点击事件,Android官方给出的文档则表示点击内容区域外的区域是否关闭窗口,那么设置为true应该就是表示关闭,设置为false就是表示不关闭咯! 那么我们就动手试一下吧,验证一下是不是和我们想象的相吻合:


实验结果似乎和我们想象的不太一样,于是试着上网找找结果看得出如下结论:setFocusable确实是让PopupWindow获得焦点,获得焦点的PopupWindow能够处理物理按钮的点击事件,否则点击事件将向上传递由Activity处理,这也能够解释为什么在setFocusable(false)的情况下点击返回按钮会退出当前Activity。关于焦点设置需要注意的一点是:如果PopupWindow中有Editor的话,focusable必须要为true。

可是还是有一点我似乎不太明白,为什么设置setOutsideTouchable(true),点击外部区域还是不会关闭窗口呢,这似乎与Google官方给出的解释有点出入,于是试着从源码寻找答案:


不看不知道,原来另有玄机,外部点击事件的响应还与backgroundDrawable有关,在backgroundDrawable!=null的情况下,PopupWindow会以backgroundDrawable作为背景生成一个根据contentView和backgroundDrawable生成一个PopupBackgroundView并返回,而如果在backgroundDrawable==null的情况下,则直接返回contentView。于是乎接着往下搜索,原来PopupBackgroundView是一个内部私有类继承至FrameLayout,且该类完成了对onKey和onTouch事件的分发处理。因为contentView我们并没有进行onKey和onTouch事件的分发处理,所以在backgroundDrawable==null的情况下,即使PopupWindow获得屏幕焦点,PopupWindow也不能处理物理按键的点击事件,因此就算点击返回按钮也会没有任何反应,更别说外部点击事件的响应了。这样也就能解释为什么在backgroundDrawable==null的情况下点击返回键或者是点击外部区域都不会关闭窗口了,因此我们在使用PopupWindow的时候都会设置焦点并且再设置一个背景,但为了不影响显示效果可以设置一个全透明背景:

setBackgroundDrawable(new ColorDrawable(0x00000000));

但是,眼尖的朋友已经发现,还有一种情况似乎解释不通在setBackgroundDrawable(new ColorDrawable(0x00000000))、setOutsideTouchable(false)、setFocusable(true)的情况下,话说背景也有了,也设置焦点了,为什么setOutsideTouchable(false)点击外部区域还是会关闭窗口呢? 说实话看了半天源码也没能明白个啥意思,好吧,这可能也是Android的一个Bug,只能想办法去解决,于是我想着是否可以重写setOutsideTouchable方法和setContentView方法来解决问题呢。在setContentView的时候,使contentView获得焦点并添加按键监听事件,于是在任何情况下都能响应返回按钮的点击事件了。在setOutsideTouchable的时候判断为true的话就设置PopupWindow的backgroundDrawable,如果backgroundDrawable==null的话,就新建一个透明背景,也不影响显示效果。

重写setContentView():

@Override

public void setContentView (View contentView) {

if(contentView !=null) {

super.setContentView(contentView);

contentView.setFocusable(true);         //

contentView.setFocusableInTouchMode(true);     //

contentView.setOnKeyListener( new  View.OnKeyListener(){

@Override

public   boolean   onKey(View v,int  keyCode, KeyEvent event){

switch(keyCode) {

case KeyEvent.KEYCODE_BACK:   

dismiss();

return  true;

default:

break;  }

return  false;   }        }); 

}

}

重写setOutsideTouchable():

@Override  

public void setOutsideTouchable(boolean touchable){

super.setOutsideTouchable(touchable);

if(touchable) {

if(mBackgroundDrawable ==null) {

mBackgroundDrawable =new ColorDrawable(0x00000000);        }

super.setBackgroundDrawable(mBackgroundDrawable);   

}else{

super.setBackgroundDrawable(null); 

}

}

尝试着运行一遍, Bingo!  完美解决!!!


PopupWindow的显示:

PopupWindow的显示大致又可以分为两类:

相对于视图中某个控件的相对位置(默认位于控件的正左下方)和相对于父控件的相对位置;

相对于视图中某个控件的相对位置:

showAsDropDown(View anchor):相对某个控件的位置(正左下方),无偏移。

showAsDropDown(View anchor, int xoff, int yoff):相对某个控件的位置,同时可以设置偏移。

showAsDropDown(View anchor, int xoff, int yoff, int gravity):相对某个控件的位置,对齐方式(尝试过,但似乎没有效果),同时可以设置偏移。

相对于父控件的相对位置:

showAtLocation(View parent, int gravity, int x, int y):相对于父控件的位置,同时可以设置偏移量。

                                                                                                        setOutsideTouchable.gif

好了,关于PopupWindow的使用就介绍到这里。如果文中有什么叙述不当的地方,希望能够指出,期待您的意见,我们一起交流。最后附上我自己尝试去写的BasePopupWindow基类,包含窗口出现和消失的一个背景渐变动画,可以使窗口出现消失显得不那么生硬,BasePopupWindow:

public class BasePopupWindow extends PopupWindow{

private Context mContext;

private float mShowAlpha =0.88f;

private Drawable mBackgroundDrawable;

public BasePopupWindow(Context context){

this.mContext = context;        

initBasePopupWindow();    }


@Override

public void setOutsideTouchable(booleantouchable){

super.setOutsideTouchable(touchable);

if(touchable) {

if(mBackgroundDrawable ==null) {                

mBackgroundDrawable =new ColorDrawable(0x00000000);      }

super.setBackgroundDrawable(mBackgroundDrawable);        

}else{

super.setBackgroundDrawable(null);  }  

}


@Override

public void setBackgroundDrawable( Drawable  background){        

mBackgroundDrawable = background;        

setOutsideTouchable(isOutsideTouchable());    }


/**

* 初始化BasePopupWindow的一些信息

* */

private void initBasePopupWindow(){  

setAnimationStyle(android.R.style.Animation_Dialog);        

setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);        

setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);        

setOutsideTouchable(true);//默认设置outside点击无响应setFocusable(true);    

}

@Override

public void setContentView(View contentView){

if(contentView !=null) {            

contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);

super.setContentView(contentView);            

addKeyListener(contentView);        }    

}


public Context  getContext(){

returnmContext;    }


@Override

public void showAtLocation(View parent,int gravity,intx,inty){

super.showAtLocation(parent, gravity, x, y);        

showAnimator().start();    

}

@Override public void showAsDropDown(View anchor){

super.showAsDropDown(anchor);       

 showAnimator().start();    

}

@Override

public void showAsDropDown(View anchor,int xoff,int yoff){

super.showAsDropDown(anchor, xoff, yoff);        

showAnimator().start();   

}

@Override

public void showAsDropDown(View anchor,intxoff,intyoff,int gravity){

super.showAsDropDown(anchor, xoff, yoff, gravity);        

showAnimator().start();    

}

@Override

public void dismiss(){

super.dismiss();

dismissAnimator().start();   

}

/**

* 窗口显示,窗口背景透明度渐变动画

* */

private ValueAnimator showAnimator(){        

ValueAnimator animator = ValueAnimator.ofFloat(1.0f, mShowAlpha);        

animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate( ValueAnimator  animation){

floatalpha = (float) animation.getAnimatedValue();                

setWindowBackgroundAlpha(alpha);            

} });        

animator.setDuration(360);

return animator;    }

/**

* 窗口隐藏,窗口背景透明度渐变动画

* */

private ValueAnimator dismissAnimator(){       

ValueAnimator animator = ValueAnimator.ofFloat(mShowAlpha,1.0f);       

animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator animation){

float alpha = (float) animation.getAnimatedValue();                

setWindowBackgroundAlpha(alpha);            }        });        

animator.setDuration(320);

return animator;   

}

/**

* 为窗体添加outside点击事件

* */

private void addKeyListener(View contentView){

if(contentView !=null) {            

contentView.setFocusable(true);            

contentView.setFocusableInTouchMode(true);            

contentView.setOnKeyListener(new View.OnKeyListener() {

@Override public boolean onKey(View view,int keyCode, KeyEvent event){

switch(keyCode) {

caseKeyEvent.KEYCODE_BACK :                            

dismiss();

return true;

default:

break;                   

}

return false;      

}           

});       

}   

}

/**

* 控制窗口背景的不透明度

* */

private void setWindowBackgroundAlpha(float alpha){        

Window window = ((Activity)getContext()).getWindow();        

WindowManager.LayoutParams layoutParams = window.getAttributes();       

layoutParams.alpha = alpha;        

window.setAttributes(layoutParams);    

}

}


原文链接:http://www.jianshu.com/p/825d1cc9fa79

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容