淺談PopupWindow在Android開發(fā)中的使用

在Android中彈出式菜單(以下稱彈窗)是使用十分廣泛一種菜單呈現(xiàn)的方式敷矫,彈窗為用戶交互提供了便利。關(guān)于彈窗的實現(xiàn)大致有以下兩種方式AlertDialog和PopupWindow颜启,當(dāng)然網(wǎng)上也有使用Activity并配合Dialog主題的方式實現(xiàn)彈窗,有興趣的朋友也可以去研究一下焕议。對于AlertDialog和PopupWindow兩者的最主要區(qū)別也有以下兩點:

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是一個以彈窗方式呈現(xiàn)的控件,可以用來顯示任意視圖(View)愉适,而且會浮動在當(dāng)前活動(activity)的頂部”犯助。因此我們可以通過PopupWindow實現(xiàn)各種各樣的彈窗效果,進(jìn)行信息的展示或者是UI交互癣漆,由于PopupWindow自定義布局比較方便维咸,而且在顯示位置比較自由不受限制,因此受到眾多開發(fā)者的青睞惠爽。

廢話不多說癌蓖,進(jìn)入正題。


PopupWindow的使用


其實PopupWindow的使用非常簡單婚肆,總的來說分為兩步:

1租副、調(diào)用PopupWindow的構(gòu)造器創(chuàng)建PopupWindow對象,并完成一些初始化設(shè)置较性。

2用僧、調(diào)用PopupWindow的showAsDropDown(View view)將PopupWindow作為View組件的下拉組件顯示出來结胀;或調(diào)用PopupWindow的showAtLocation()方法將PopupWindow在指定位置顯示出來。

創(chuàng)建并完成初始化設(shè)置:

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三者必須實現(xiàn),否則將不會顯示任何視圖院仿。

?setwidth和setHeight的參數(shù)值可以是具體的數(shù)值秸抚,也可以是MATCH_PARENT或者是WRAP_CONTENT。

setContentView則是為PopupWindow設(shè)置視圖內(nèi)容歹垫。

? setFocusable顧名思義就是讓PopupWindow獲得焦點剥汤。

?setBackgroundDrawable從字面理解就是為PopupWindow設(shè)置一個背景。?

setOutsideTouchable則表示PopupWindow內(nèi)容區(qū)域外的區(qū)域是否響應(yīng)點擊事件排惨,Android官方給出的文檔則表示點擊內(nèi)容區(qū)域外的區(qū)域是否關(guān)閉窗口吭敢,那么設(shè)置為true應(yīng)該就是表示關(guān)閉,設(shè)置為false就是表示不關(guān)閉咯暮芭! 那么我們就動手試一下吧省有,驗證一下是不是和我們想象的相吻合:


實驗結(jié)果似乎和我們想象的不太一樣,于是試著上網(wǎng)找找結(jié)果看得出如下結(jié)論:setFocusable確實是讓PopupWindow獲得焦點谴麦,獲得焦點的PopupWindow能夠處理物理按鈕的點擊事件蠢沿,否則點擊事件將向上傳遞由Activity處理,這也能夠解釋為什么在setFocusable(false)的情況下點擊返回按鈕會退出當(dāng)前Activity匾效。關(guān)于焦點設(shè)置需要注意的一點是:如果PopupWindow中有Editor的話舷蟀,focusable必須要為true。

可是還是有一點我似乎不太明白面哼,為什么設(shè)置setOutsideTouchable(true)野宜,點擊外部區(qū)域還是不會關(guān)閉窗口呢,這似乎與Google官方給出的解釋有點出入魔策,于是試著從源碼尋找答案:


不看不知道匈子,原來另有玄機,外部點擊事件的響應(yīng)還與backgroundDrawable有關(guān)闯袒,在backgroundDrawable!=null的情況下虎敦,PopupWindow會以backgroundDrawable作為背景生成一個根據(jù)contentView和backgroundDrawable生成一個PopupBackgroundView并返回,而如果在backgroundDrawable==null的情況下政敢,則直接返回contentView其徙。于是乎接著往下搜索,原來PopupBackgroundView是一個內(nèi)部私有類繼承至FrameLayout喷户,且該類完成了對onKey和onTouch事件的分發(fā)處理唾那。因為contentView我們并沒有進(jìn)行onKey和onTouch事件的分發(fā)處理,所以在backgroundDrawable==null的情況下褪尝,即使PopupWindow獲得屏幕焦點闹获,PopupWindow也不能處理物理按鍵的點擊事件期犬,因此就算點擊返回按鈕也會沒有任何反應(yīng),更別說外部點擊事件的響應(yīng)了避诽。這樣也就能解釋為什么在backgroundDrawable==null的情況下點擊返回鍵或者是點擊外部區(qū)域都不會關(guān)閉窗口了哭懈,因此我們在使用PopupWindow的時候都會設(shè)置焦點并且再設(shè)置一個背景,但為了不影響顯示效果可以設(shè)置一個全透明背景:

setBackgroundDrawable(new ColorDrawable(0x00000000));

但是茎用,眼尖的朋友已經(jīng)發(fā)現(xiàn)遣总,還有一種情況似乎解釋不通在setBackgroundDrawable(new ColorDrawable(0x00000000))、setOutsideTouchable(false)轨功、setFocusable(true)的情況下旭斥,話說背景也有了,也設(shè)置焦點了古涧,為什么setOutsideTouchable(false)點擊外部區(qū)域還是會關(guān)閉窗口呢垂券? 說實話看了半天源碼也沒能明白個啥意思,好吧羡滑,這可能也是Android的一個Bug菇爪,只能想辦法去解決,于是我想著是否可以重寫setOutsideTouchable方法和setContentView方法來解決問題呢柒昏。在setContentView的時候凳宙,使contentView獲得焦點并添加按鍵監(jiān)聽事件,于是在任何情況下都能響應(yīng)返回按鈕的點擊事件了职祷。在setOutsideTouchable的時候判斷為true的話就設(shè)置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的顯示大致又可以分為兩類:

相對于視圖中某個控件的相對位置(默認(rèn)位于控件的正左下方)和相對于父控件的相對位置兜辞;

相對于視圖中某個控件的相對位置:

showAsDropDown(View anchor):相對某個控件的位置(正左下方),無偏移陨囊。

showAsDropDown(View anchor, int xoff, int yoff):相對某個控件的位置弦疮,同時可以設(shè)置偏移夹攒。

showAsDropDown(View anchor, int xoff, int yoff, int gravity):相對某個控件的位置蜘醋,對齊方式(嘗試過,但似乎沒有效果)咏尝,同時可以設(shè)置偏移压语。

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

showAtLocation(View parent, int gravity, int x, int y):相對于父控件的位置啸罢,同時可以設(shè)置偏移量。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? setOutsideTouchable.gif

好了胎食,關(guān)于PopupWindow的使用就介紹到這里扰才。如果文中有什么敘述不當(dāng)?shù)牡胤剑M軌蛑赋霾蘖诖囊庖婑孟唬覀円黄鸾涣鳌W詈蟾缴衔易约簢L試去寫的BasePopupWindow基類粥航,包含窗口出現(xiàn)和消失的一個背景漸變動畫琅捏,可以使窗口出現(xiàn)消失顯得不那么生硬,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);//默認(rèn)設(shè)置outside點擊無響應(yīng)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.reibang.com/p/825d1cc9fa79

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市缀程,隨后出現(xiàn)的幾起案子搜吧,更是在濱河造成了極大的恐慌,老刑警劉巖杨凑,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滤奈,死亡現(xiàn)場離奇詭異,居然都是意外死亡撩满,警方通過查閱死者的電腦和手機僵刮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鹦牛,“玉大人搞糕,你說我怎么就攤上這事÷罚” “怎么了窍仰?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長礼殊。 經(jīng)常有香客問我驹吮,道長,這世上最難降的妖魔是什么晶伦? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任碟狞,我火速辦了婚禮,結(jié)果婚禮上婚陪,老公的妹妹穿的比我還像新娘族沃。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布脆淹。 她就那樣靜靜地躺著常空,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盖溺。 梳的紋絲不亂的頭發(fā)上漓糙,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音烘嘱,去河邊找鬼昆禽。 笑死,一個胖子當(dāng)著我的面吹牛蝇庭,可吹牛的內(nèi)容都是我干的为狸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼遗契,長吁一口氣:“原來是場噩夢啊……” “哼辐棒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起牍蜂,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤漾根,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鲫竞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辐怕,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年从绘,在試婚紗的時候發(fā)現(xiàn)自己被綠了寄疏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡僵井,死狀恐怖陕截,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情批什,我是刑警寧澤农曲,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站驻债,受9級特大地震影響乳规,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜合呐,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一暮的、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧淌实,春花似錦冻辩、人聲如沸猖腕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谈息。三九已至缘屹,卻和暖如春凛剥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背轻姿。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工犁珠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人互亮。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓犁享,卻偏偏與公主長得像,于是被迫代替她去往敵國和親豹休。 傳聞我的和親對象是個殘疾皇子炊昆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容