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

騰訊微信

在Android中彈出式菜單(以下稱彈窗)是使用十分廣泛一種菜單呈現(xiàn)的方式规个,彈窗為用戶交互提供了便利肩狂。關于彈窗的實現(xiàn)大致有以下兩種方式AlertDialog和PopupWindow贼邓,當然網(wǎng)上也有使用Activity并配合Dialog主題的方式實現(xiàn)彈窗铅碍,有興趣的朋友也可以去研究一下。對于AlertDialog和PopupWindow兩者的最主要區(qū)別就是顯示的位置問題:

  • 位置是否固定捌治。 AlertDialog在位置顯示上是固定的岗钩,而PopupWindow則相對比較隨意,能夠在主屏幕上的任意位置顯示肖油。

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)森枪,而且會浮動在當前活動(activity)的頂部”视搏。因此我們可以通過PopupWindow實現(xiàn)各種各樣的彈窗效果,進行信息的展示或者是UI交互,由于PopupWindow自定義布局比較方便县袱,而且在顯示位置比較自由不受限制浑娜,因此受到眾多開發(fā)者的青睞。

廢話不多說式散,進入正題筋遭。

PopupWindow的使用

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

  • 1暴拄、調用PopupWindow的構造器創(chuàng)建PopupWindow對象宛畦,并完成一些初始化設置。
  • 2揍移、調用PopupWindow的showAsDropDown(View view)將PopupWindow作為View組件的下拉組件顯示出來次和;或調用PopupWindow的showAtLocation()方法將PopupWindow在指定位置顯示出來。
創(chuàng)建并完成初始化設置:
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設置視圖內容诉探。 setFocusable顧名思義就是讓PopupWindow獲得焦點日熬。 setBackgroundDrawable從字面理解就是為PopupWindow設置一個背景。 setOutsideTouchable則表示PopupWindow內容區(qū)域外的區(qū)域是否響應點擊事件肾胯,Android官方給出的文檔則表示點擊內容區(qū)域外的區(qū)域是否關閉窗口竖席,那么設置為true應該就是表示關閉耘纱,設置為false就是表示不關閉咯! 那么我們就動手試一下吧毕荐,驗證一下是不是和我們想象的相吻合:

QQ截圖

實驗結果似乎和我們想象的不太一樣束析,于是試著上網(wǎng)找找結果看得出如下結論:setFocusable確實是讓PopupWindow獲得焦點,獲得焦點的PopupWindow能夠處理物理按鈕的點擊事件憎亚,否則點擊事件將向上傳遞由Activity處理员寇,這也能夠解釋為什么在setFocusable(false)的情況下點擊返回按鈕會退出當前Activity。關于焦點設置需要注意的一點是:****如果PopupWindow中有Editor的話第美,focusable必須要為true蝶锋。****

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

源碼截圖

不看不知道恶守,原來另有玄機第献,外部點擊事件的響應還backgroundDrawable有關,在backgroundDrawable!=null的情況下兔港,PopupWindow會以backgroundDrawable作為背景生成一個根據(jù)contentView和backgroundDrawable生成一個PopupBackgroundView并返回庸毫,而如果在backgroundDrawable==null的情況下,則直接返回contentView衫樊。于是乎接著往下搜索飒赃,原來PopupBackgroundView是一個內部私有類繼承至FrameLayout,且該類完成了對onKey和onTouch事件的分發(fā)處理科侈。因為contentView我們并沒有進行onKey和onTouch事件的分發(fā)處理载佳,所以在backgroundDrawable!=null的情況下,即使PopupWindow獲得屏幕焦點臀栈,PopupWindow也不能處理物理按鍵的點擊事件蔫慧,因此就算點擊返回按鈕也會沒有任何反應,更別說外部點擊事件的響應了权薯。這樣也就能解釋為什么在backgroundDrawable!=null的情況下點擊返回鍵或者是點擊外部區(qū)域都不會關閉窗口了姑躲,因此我們在使用PopupWindow的時候都會設置焦點并且再設置一個背景,但為了不影響顯示效果可以設置一個全透明背景:

 setBackgroundDrawable(new ColorDrawable(0x00000000));

但是盟蚣,眼尖的朋友已經發(fā)現(xiàn)黍析,還有一種情況似乎解釋不通在setBackgroundDrawable(new ColorDrawable(0x00000000))、setOutsideTouchable(false)屎开、setFocusable(true)的情況下阐枣,話說背景也有了,也設置焦點了,為什么setOutsideTouchable(false)點擊外部區(qū)域還是會關閉窗口呢蔼两? 說實話看了半天源碼也沒能明白個啥意思甩鳄,好吧,這可能也是Android的一個Bug宪哩,只能想辦法去解決娩贷,于是我想著是否可以重寫setOutsideTouchable方法和setContentView方法來解決問題呢第晰。在setContentView的時候锁孟,使contentView獲得焦點并添加按鍵監(jiān)聽事件,于是在任何情況下都能響應返回按鈕的點擊事件了茁瘦。在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腔稀! 完美解決E桕肌!焊虏!

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的使用就介紹到這里贸宏。如果文中有什么敘述不當?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(boolean touchable) {
        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() {
        return mContext;
    }

    @Override
    public void showAtLocation(View parent, int gravity, int x, int y) {
        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, int xoff, int yoff, 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) {
                float alpha = (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) {
                        case KeyEvent.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);
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末姊舵,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子寓落,更是在濱河造成了極大的恐慌括丁,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伶选,死亡現(xiàn)場離奇詭異史飞,居然都是意外死亡,警方通過查閱死者的電腦和手機仰税,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門构资,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人陨簇,你說我怎么就攤上這事吐绵。” “怎么了河绽?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵己单,是天一觀的道長。 經常有香客問我耙饰,道長纹笼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任榔幸,我火速辦了婚禮允乐,結果婚禮上,老公的妹妹穿的比我還像新娘削咆。我一直安慰自己牍疏,他們只是感情好,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布拨齐。 她就那樣靜靜地躺著鳞陨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瞻惋。 梳的紋絲不亂的頭發(fā)上厦滤,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天,我揣著相機與錄音歼狼,去河邊找鬼掏导。 笑死,一個胖子當著我的面吹牛羽峰,可吹牛的內容都是我干的趟咆。 我是一名探鬼主播添瓷,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼值纱!你這毒婦竟也來了鳞贷?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤虐唠,失蹤者是張志新(化名)和其女友劉穎搀愧,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疆偿,經...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡咱筛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了翁脆。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眷蚓。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡鼻种,死狀恐怖反番,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情叉钥,我是刑警寧澤罢缸,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站投队,受9級特大地震影響枫疆,放射性物質發(fā)生泄漏。R本人自食惡果不足惜敷鸦,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一息楔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扒披,春花似錦值依、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至价说,卻和暖如春辆亏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鳖目。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工扮叨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人领迈。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓彻磁,卻偏偏與公主長得像甸鸟,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子兵迅,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內容

  • 在Android中彈出式菜單(以下稱彈窗)是使用十分廣泛一種菜單呈現(xiàn)的方式抢韭,彈窗為用戶交互提供了便利。關于彈窗的實...
    OzanShareing閱讀 3,126評論 2 10
  • ¥開啟¥ 【iAPP實現(xiàn)進入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程恍箭,因...
    小菜c閱讀 6,440評論 0 17
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,264評論 25 707
  • 本文出自 “阿敏其人” 簡書博客刻恭,轉載或引用請注明出處。 前言 注:為書寫方便扯夭,本文的popup代表PopupWi...
    阿敏其人閱讀 5,227評論 3 26
  • 最近覺察到自己很容易就陷入了頭腦的胡思亂想之中鳍贾!這樣可以隔離自己的情緒和感受!讓我一直活在過去設置的自動化模式之中...
    竺子閱讀 352評論 0 0