【Android】在任意位置彈出PopupWindow

又是一張毫不相關(guān)的圖

前言

在日常的開發(fā)中溉卓,經(jīng)常會有彈框的操作皮迟。實現(xiàn)彈框有兩種選,PopupWindow或者Dialog桑寨,這里就先忽略Dialog伏尼。彈框可能會在各種位置出現(xiàn),在指定View的上尉尾、下爆阶、左、右沙咏、左對齊辨图、右對齊等...
PopupWindow似乎就提供了showAsDropDown方法(請忽略showAtLocation,這邊說的是相對于View顯示)肢藐,這~~就有點尷尬了故河。

PopupWindow

平時我們可能是這樣用PopupWindow的:

  • 創(chuàng)建一個布局,再創(chuàng)建一個類繼承PopupWindow
public class TestPopupWindow extends PopupWindow {

    public TestPopupWindow(Context context) {
        super(context);
        setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
        setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
        setOutsideTouchable(true);
        setFocusable(true);
        setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        View contentView = LayoutInflater.from(context).inflate(R.layout.popup_test, 
              null, false);
        setContentView(contentView);
    }
}
  • 然后直接使用它
TestPopupWindow mWindow = new TestPopupWindow(this);
//根據(jù)指定View定位
PopupWindowCompat.showAsDropDown(mWindow, mButtom, 0, 0, Gravity.START);
//或者
mWindow.showAsDropDown(...);
//又或者使用showAtLocation根據(jù)屏幕來定位
mWindow.showAtLocation(...);

Gravity.LEFT(Gravity.START):相對于View左對齊吆豹;
Gravity.RIGHT(Gravity.END):相對于View靠右顯示鱼的。

Gravity.CENTER:在showAsDropDown()中是跟 Gravity.LEFT一樣理盆,在showAtLocation()中Gravity.CENTER才有效果

  • 得到效果


    left.gif

查了下showAsDropDown(),發(fā)現(xiàn)只能在指定控件的下面彈出凑阶,總感覺少了點什么~~
有時候我想彈在View的上面猿规、左邊、右邊晌砾?怎么解坎拐?

可能有機智的boy已經(jīng)想到了showAsDropDown()中的另外兩個參數(shù),xoff养匈、yoff呕乎。

要利用這兩個參數(shù)帝璧,不過不建議在代碼中直接寫。為什么诈闺?
如果你的PopupWindow寬高不確定雅镊,這兩個參數(shù)你也不知道該寫多少。
什么!你的PopupWindow寬高都寫死了?騷年震叮,你還是太年輕了偿乖。當你寬高確定的時候击罪,如果PopupWindow中有文本眠副,用戶把字體改得超大娃弓,你的字就沒掉一塊了
什么!你還是嘴硬非要把寬高寫死变汪?好吧,你贏了童芹。

各種位置的彈窗

下面就來利用xoffyoff在你想要的任何位置彈框慕匠。

準備工作

彈框前,需要得到PopupWindow的大小(也就是PopupWindowcontentView的大小)酪呻。
由于contentView還未繪制减宣,這時候的widthheight都是0玩荠。因此需要通過measure測量出contentView的大小漆腌,才能進行計算。需要如下方法:

    @SuppressWarnings("ResourceType")
    private static int makeDropDownMeasureSpec(int measureSpec) {
        int mode;
        if (measureSpec == ViewGroup.LayoutParams.WRAP_CONTENT) {
            mode = View.MeasureSpec.UNSPECIFIED;
        } else {
            mode = View.MeasureSpec.EXACTLY;
        }
        return View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.getSize(measureSpec), mode);
    }

測量contentView的大小

TestPopupWindow window = new TestPopupWindow(this);
View contentView = window.getContentView();
//需要先測量阶冈,PopupWindow還未彈出時闷尿,寬高為0
contentView.measure(makeDropDownMeasureSpec(window.getWidth()), 
      makeDropDownMeasureSpec(window.getHeight()));

彈框

測量好PopupWindow大小后,就在任意位置彈窗了
彈框的位置無非就是根據(jù)PopupWindow以及指定View的大小眼溶,計算水平悠砚、豎直方向偏移。

水平:居左堂飞;豎直:居下

計算偏移:


水平:居左灌旧;豎直:居下

代碼、效果:

int offsetX = -window.getContentView().getMeasuredWidth();
int offsetY = 0;
PopupWindowCompat.showAsDropDown(window, mButton, offsetX, offsetY, Gravity.START);
效果(水平:居左绰筛;豎直:居下)
水平:居中枢泰;豎直:居下

計算偏移:


水平:居中;豎直:居下

代碼铝噩、效果:

offsetX = Math.abs(mWindow.getContentView().getMeasuredWidth()-mButton.getWidth()) / 2;
offsetY = 0;
PopupWindowCompat.showAsDropDown(window, mButton, offsetX, offsetY, Gravity.START);
效果(水平:居中衡蚂;豎直:居下)
水平:右對齊;豎直:居下

計算偏移:


水平:右對齊骏庸;豎直:居下

代碼毛甲、效果:

offsetX = mButton.getWidth() - mWindow.getContentView().getMeasuredWidth();
offsetY = 0;
PopupWindowCompat.showAsDropDown(window, mButton, offsetX, offsetY, Gravity.START);
效果(水平:右對齊;豎直:居下)
水平:居中具被;豎直:居上

計算偏移:


水平:居中玻募;豎直:居上

代碼、效果:

offsetX = Math.abs(mWindow.getContentView().getMeasuredWidth()-mButton.getWidth()) / 2;
offsetY = -(mWindow.getContentView().getMeasuredHeight()+mButton.getHeight());
PopupWindowCompat.showAsDropDown(window, mButton, offsetX, offsetY, Gravity.START);
效果(水平:居中一姿;豎直:居上)
水平:居左七咧;豎直:居中

計算偏移:


水平:居左;豎直:居中

代碼叮叹、效果:

offsetX = -mWindow.getContentView().getMeasuredWidth();
offsetY = -(mWindow.getContentView().getMeasuredHeight() + mButton.getHeight()) / 2;
PopupWindowCompat.showAsDropDown(window, mButton, offsetX, offsetY, Gravity.START);
效果(水平:居左艾栋;豎直:居中)
水平:居右;豎直:居中

計算偏移:


水平:居右蛉顽;豎直:居中

代碼蝗砾、效果:

offsetX = 0;
offsetY = -(mWindow.getContentView().getMeasuredHeight() + mButton.getHeight()) / 2;
PopupWindowCompat.showAsDropDown(window, mButton, offsetX, offsetY, Gravity.END);
center-right.gif

畫這些圖比敲代碼還累~~~
基本上完成了所有位置的彈框。還有一些位置上面沒提到,不過通過上面那些水平悼粮、豎直的偏移也能拼湊出來拇泣。

背景變暗

說完位置方案,順便提下背景色的變化方案矮锈。
通改變Window的透明度來實現(xiàn)背景變暗是常用的一種做法霉翔。
PopupWindow中,先寫個修改Window透明度的方法(注意苞笨,這邊的mContext必須是Activity

    /**
     * 控制窗口背景的不透明度
     */
    private void setWindowBackgroundAlpha(float alpha) {
        if (mContext == null) return;
        if (mContext instanceof Activity) {
            Window window = ((Activity) mContext).getWindow();
            WindowManager.LayoutParams layoutParams = window.getAttributes();
            layoutParams.alpha = alpha;
            window.setAttributes(layoutParams);
        }
    }

然后定義透明度

private float mAlpha = 1f; //背景灰度  0-1  1表示全透明

最后在PopupWindow show的時候調(diào)用以下方法债朵。

    /**
     * 窗口顯示,窗口背景透明度漸變動畫
     */
    private void showBackgroundAnimator() {
        if (mAlpha >= 1f) return;
        ValueAnimator animator = ValueAnimator.ofFloat(1.0f, mAlpha);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float alpha = (float) animation.getAnimatedValue();
                setWindowBackgroundAlpha(alpha);
            }
        });
        animator.setDuration(360);
        animator.start();
    }

通過動畫來改變Window達到漸變的效果瀑凝。

已有的庫

這么麻煩的彈框序芦,當然有人已經(jīng)為我們封裝好了

  • RelativePopupWindow:代碼簡潔,支持各種位置的彈框粤咪。還能超出屏幕(感覺用不上)谚中。
    用起來是這樣的:
popup.showOnAnchor(anchor, VerticalPosition.ABOVE, HorizontalPosition.CENTER, false);
  • EasyPopup:一個功能比較全的庫,支持背景變暗寥枝,背景不可點擊(6.0以上通用)等...而且可以鏈式調(diào)用哦宪塔。不過有個缺點:背景變暗效果只支持 4.2 以上的版本。
    用起來是這樣的:
private EasyPopup mCirclePop;
circlePop = new EasyPopup(this)
        .setContentView(R.layout.layout_circle_comment)
        .setAnimationStyle(R.style.CirclePopAnim)
    //是否允許點擊PopupWindow之外的地方消失
        .setFocusAndOutsideEnable(true)
        .createPopup();
//顯示
circlePop.showAtAnchorView(view, VerticalGravity.CENTER, HorizontalGravity.LEFT, 0, 0);

這兩個庫的跟我上面的思路基本一樣囊拜,然后通過水平某筐、豎直參數(shù)來使用。
用的話冠跷,如果最小版本大于等于18的話南誊,直接用- EasyPopup就可以了。(畢竟是國人寫的蜜托,有中文文檔~~)

自己寫個工具類

介于EasyPopup只適配4.2 以上的版本抄囚,而項目要適配到4.1。只好自己寫一個了~~
結(jié)合上面的提到的兩個庫橄务,以及背景變暗的方案幔托。改造一個自己的庫。具體的實現(xiàn)代碼就不貼出來了仪糖,用法也借鑒了上面的兩個庫柑司。

 SmartPopupWindow popupWindow= SmartPopupWindow.Builder
     .build(Activity.this, view)
     .setAlpha(0.4f)                   //背景灰度     默認全透明
     .setOutsideTouchDismiss(false)    //點擊外部消失  默認true(消失)
     .createPopupWindow();             //創(chuàng)建PopupWindow
 popupWindow.showAtAnchorView(view, VerticalPosition.ABOVE, HorizontalPosition.CENTER);

水平方向參數(shù)HorizontalPositionLEFT 迫肖、 RIGHT 锅劝、 ALIGN_LEFT 、 ALIGN_RIGHT蟆湖、 CENTER
豎直方向參數(shù)VerticalPositionABOVE 故爵、 BELOW、 ALIGN_TOP 、 ALIGN_BOTTOM诬垂、 CENTER

項目地址SmartPopupWindow

功能很簡單劲室,由于是通過別人的庫改造的,就不上傳JCenter了结窘。
里面就三個文件很洋,想用的話去拷下來就可以了。

參考

RelativePopupWindow
EasyPopup
Android彈窗_PopupWindow詳解 (挺詳細的)

以上有錯誤之處隧枫,感謝指出

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喉磁,一起剝皮案震驚了整個濱河市适掰,隨后出現(xiàn)的幾起案子五嫂,更是在濱河造成了極大的恐慌颇玷,老刑警劉巖瞳脓,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件睬澡,死亡現(xiàn)場離奇詭異疗认,居然都是意外死亡蜓氨,警方通過查閱死者的電腦和手機只搁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門赤兴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來妖滔,“玉大人,你說我怎么就攤上這事桶良☆蹰梗” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵艺普,是天一觀的道長簸州。 經(jīng)常有香客問我,道長歧譬,這世上最難降的妖魔是什么岸浑? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮瑰步,結(jié)果婚禮上矢洲,老公的妹妹穿的比我還像新娘。我一直安慰自己缩焦,他們只是感情好读虏,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著袁滥,像睡著了一般盖桥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上题翻,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天揩徊,我揣著相機與錄音,去河邊找鬼。 笑死塑荒,一個胖子當著我的面吹牛熄赡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播齿税,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼彼硫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了凌箕?” 一聲冷哼從身側(cè)響起乌助,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎陌知,沒想到半個月后他托,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡仆葡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年赏参,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沿盅。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡把篓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出腰涧,到底是詐尸還是另有隱情韧掩,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布窖铡,位于F島的核電站疗锐,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏费彼。R本人自食惡果不足惜滑臊,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望箍铲。 院中可真熱鬧雇卷,春花似錦、人聲如沸颠猴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽翘瓮。三九已至贮折,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間春畔,已是汗流浹背脱货。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留律姨,地道東北人振峻。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像择份,于是被迫代替她去往敵國和親扣孟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,510評論 25 707
  • ¥開啟¥ 【iAPP實現(xiàn)進入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程荣赶,因...
    小菜c閱讀 6,358評論 0 17
  • 在Android中彈出式菜單(以下稱彈窗)是使用十分廣泛一種菜單呈現(xiàn)的方式凤价,彈窗為用戶交互提供了便利。關(guān)于彈窗的實...
    OzanShareing閱讀 3,106評論 2 10
  • 最近比較偷懶哈拔创,已經(jīng)有一段時間沒有更新分享了利诺,一個原因也是最近需要接觸前端的一些知識,另外也是項目比較忙剩燥,周末事情...
    juexingzhe閱讀 26,947評論 1 15
  • 今天周老師不在的第四天了慢逾,下午周老師就應該回來啦。 今天上午灭红,基本上了體育課和音樂課等侣滩。中午數(shù)學老師還是像昨天和前...
    嘉榮123閱讀 196評論 0 0