一句話搞定PopupWindow

前言

設(shè)計(jì)圖給出的效果


最終的效果


最終代碼
common_tv_right_more 的點(diǎn)擊事件

    if (view.getId() == R.id.common_tv_right_more) { 
        new PopTop.Builder(this)
                .setView(findViewById(R.id.common_tv_right_more)) // 在某個(gè)空間的下面 
                .setPopTopOnClick(new PopTop.PopTopOnClick() {
                    @Override
                    public void EditOnclick() {
                        /* 編輯 監(jiān)聽*/ 
                    } 
                    @Override
                    public void DelOnclick() {
                        /*刪除 監(jiān)聽*/
                    } 
                })
                .show();
    }

接著開始想和學(xué)習(xí)怎么做了。

使用場景

PopupWindow屏鳍,顧名思義悯森,就是彈窗盅蝗,在很多場景下都可以見到它议薪。例如ActionBar/Toolbar的選項(xiàng)彈窗尤蛮,一組選項(xiàng)的容器,或者列表等集合的窗口等等斯议。

基本使用

使用PopupWindow很簡單,可以總結(jié)為三個(gè)步驟:

1醇锚、創(chuàng)建PopupWindow對象實(shí)例哼御;

2、設(shè)置背景焊唬、注冊事件監(jiān)聽器和添加動畫恋昼;

3、顯示PopupWindow赶促。

// 用于PopupWindow的View
View contentView=LayoutInflater.from(context).inflate(layoutRes, null, false);
// 創(chuàng)建PopupWindow對象液肌,其中:
// 第一個(gè)參數(shù)是用于PopupWindow中的View,第二個(gè)參數(shù)是PopupWindow的寬度鸥滨,
// 第三個(gè)參數(shù)是PopupWindow的高度嗦哆,第四個(gè)參數(shù)指定PopupWindow能否獲得焦點(diǎn)
PopupWindow window=new PopupWindow(contentView, 100, 100, true);
// 設(shè)置PopupWindow的背景
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
// 設(shè)置PopupWindow是否能響應(yīng)外部點(diǎn)擊事件
window.setOutsideTouchable(true);
// 設(shè)置PopupWindow是否能響應(yīng)點(diǎn)擊事件
window.setTouchable(true);
// 顯示PopupWindow谤祖,其中:
// 第一個(gè)參數(shù)是PopupWindow的錨點(diǎn),第二和第三個(gè)參數(shù)分別是PopupWindow相對錨點(diǎn)的x老速、y偏移
window.showAsDropDown(anchor, xoff, yoff);
// 或者也可以調(diào)用此方法顯示PopupWindow粥喜,其中:
// 第一個(gè)參數(shù)是PopupWindow的父View,第二個(gè)參數(shù)是PopupWindow相對父View的位置橘券,
// 第三和第四個(gè)參數(shù)分別是PopupWindow相對父View的x额湘、y偏移
// window.showAtLocation(parent, gravity, x, y);

使用showAsDropDown方法顯示PopupWindow

通常情況下,調(diào)用showAsDropDown方法后PopupWindow將會在錨點(diǎn)的左下方顯示(drop down)旁舰。但是锋华,有時(shí)想讓PopupWindow在錨點(diǎn)的上方顯示,或者在錨點(diǎn)的中間位置顯示箭窜,此時(shí)就需要用到showAsDropDown方法的xoff和yoff參數(shù)了毯焕。

這里我們的目的不僅包括上面提到的兩種情況(錨點(diǎn)上方或錨點(diǎn)中部),而是囊括了水平和垂直方向各5種顯示方式:

  1. 水平方向:
    • ALIGN_LEFT:在錨點(diǎn)內(nèi)部的左邊绽快;
    • ALIGN_RIGHT:在錨點(diǎn)內(nèi)部的右邊芥丧;
    • CENTER_HORI:在錨點(diǎn)水平中部;
    • TO_RIGHT:在錨點(diǎn)外部的右邊坊罢;
    • TO_LEFT:在錨點(diǎn)外部的左邊续担。
  1. 垂直方向:
    • ALIGN_ABOVE:在錨點(diǎn)內(nèi)部的上方;
    • ALIGN_BOTTOM:在錨點(diǎn)內(nèi)部的下方活孩;
    • CENTER_VERT:在錨點(diǎn)垂直中部物遇;
    • TO_BOTTOM:在錨點(diǎn)外部的下方;
    • TO_ABOVE:在錨點(diǎn)外部的上方憾儒。

下面來看張圖:


showAsDropDown 可以做哪些效果

想要的效果 是在某個(gè)控件的下面 并且還要有偏移 角度询兴,所以在偏移角度上
大體是多少再去調(diào)試。
所以在主要的方法落在了showAsDropDown 的下面起趾。 想要寫成通用的也不是不可以的诗舰,先不要想這么多,先去做训裆,實(shí)現(xiàn)目前的效果再說眶根。

由上面的基本方法中我可以看到
View contentView=LayoutInflater.from(context).inflate(layoutRes, null, false);

這個(gè)view 的產(chǎn)出,所以就可以把它單獨(dú)拿起來边琉,在單獨(dú)頁面中和xml 處理属百,底色背景和角度了,
以及整個(gè)view 中的點(diǎn)擊事件處理变姨,并且顯得不那么啰嗦了族扰,看簡單的代碼,就用到build設(shè)計(jì)模式,
突出了這個(gè)模式的優(yōu)點(diǎn)渔呵,不斷添加 各個(gè)設(shè)置條件怒竿。

build設(shè)計(jì)模式

自己之前也寫過關(guān)于設(shè)計(jì)模式的問題。

首先看是build 的時(shí)候創(chuàng)建view 或者說產(chǎn)出view 為了提供顯示用

public PopTop(Builder builder) {
    //窗口布局
    super(builder.context);
    this.builder = builder;
    Create();
}

public void Create() {
    //窗口布局
    setContentView(mainView = LayoutInflater.from(builder.context).inflate(R.layout.pop_top, null));
    //設(shè)置寬度
    setWidth(dip2px(builder.context, 100));
    //設(shè)置高度
    setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
    setTouchable(true);
    setFocusable(true);
    //設(shè)置顯示隱藏動畫
    //        setAnimationStyle(R.style.AnimTools);
    //設(shè)置背景透明
    setBackgroundDrawable(new ColorDrawable());
    /*          //監(jiān)聽窗口的焦點(diǎn)事件厘肮,點(diǎn)擊窗口外面則取消顯示*/
    getContentView().setOnFocusChangeListener(new View.OnFocusChangeListener() {

        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            if (!hasFocus) {
                dismiss();
            }
        }
    });
    //初始化 控件點(diǎn)擊事件處理
    initView(mainView);
}

再看build 做了哪些處理

public static class Builder {
    protected final Context context;
    protected View view; 

    PopTopOnClick popTopOnClick;

    public Builder setPopTopOnClick(PopTopOnClick popTopOnClick) {
        this.popTopOnClick = popTopOnClick;
        return this;
    } 

    public Builder setView(View view) {
        this.view = view;
        return this;
    }

    public Builder(@NonNull Context context) {
        this.context = context;
    }

    @UiThread
    public PopupWindow build() {
        return new PopTop(this);
    }

    @UiThread
    public PopupWindow show() {
        PopupWindow popTop = build();
        int windowPos[] = calculatePopWindowPos(view);
        windowPos[0] -= 15; //x 軸向左偏移15像素
        windowPos[1] -= 10; //y 軸向上偏移10像素
        popTop.showAtLocation(view, Gravity.TOP | Gravity.START, windowPos[0], windowPos[1]);
        return popTop;
    }
}

主要在show 中

對偏移量的處理愧口,并且在合適的位置處理,在上面基本中說道类茂。

/**
 * 計(jì)算出來的位置耍属,y方向就在anchorView的上面和下面對齊顯示,x方向就是與屏幕右邊對齊顯示
 * 如果anchorView的位置有變化巩检,就可以適當(dāng)自己額外加入偏移來修正
 *
 * @param anchorView 呼出window的view
 * @return window顯示的左上角的xOff, yOff坐標(biāo)
 */
public static int[] calculatePopWindowPos(final View anchorView) {

    final int windowPos[] = new int[2];
    final int anchorLoc[] = new int[2];
    // 獲取錨點(diǎn)View在屏幕上的左上角坐標(biāo)位置
    anchorView.getLocationOnScreen(anchorLoc);
    final int anchorHeight = anchorView.getHeight();
    // 獲取屏幕的高寬
    final int screenHeight = getScreenHeight(anchorView.getContext());
    final int screenWidth = getScreenWidth(anchorView.getContext());
    mainView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
    // 計(jì)算contentView的高寬
    final int windowHeight = mainView.getMeasuredHeight();
    final int windowWidth = mainView.getMeasuredWidth();
    // 判斷需要向上彈出還是向下彈出顯示
    final boolean isNeedShowUp = (screenHeight - anchorLoc[1] - anchorHeight < windowHeight);
    if (isNeedShowUp) {
        windowPos[0] = screenWidth - windowWidth;
        windowPos[1] = anchorLoc[1] - windowHeight;
    } else {
        windowPos[0] = screenWidth - windowWidth;
        windowPos[1] = anchorLoc[1] + anchorHeight;
    }
    return windowPos;
}

大體流程就是這些了厚骗,至于點(diǎn)擊事件,進(jìn)行事件監(jiān)聽兢哭,也是從產(chǎn)生的pop的時(shí)候傳遞過來的领舰。

所有代碼

import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.support.annotation.NonNull;
import android.support.annotation.UiThread;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.TextView;



public class PopTop extends PopupWindow implements View.OnClickListener {

    public static View mainView;
    protected final Builder builder;
    protected TextView popTvCustomerEdit;
    protected TextView popTvCustomerDel;
    protected LinearLayout popLlEdit;
    protected TextView popTvCustomerSys;
    protected TextView popTvCustomerCreate;
    protected LinearLayout popLlCustomer;

    public PopTop(Builder builder) {
        //窗口布局
        super(builder.context);
        this.builder = builder;
        Create();
    }

    public void Create() {
        //窗口布局
        setContentView(mainView = LayoutInflater.from(builder.context).inflate(R.layout.pop_top, null));
        //設(shè)置寬度
        setWidth(dip2px(builder.context, 100));
        //設(shè)置高度
        setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
        setTouchable(true);
        setFocusable(true);
        //設(shè)置顯示隱藏動畫
//        setAnimationStyle(R.style.AnimTools);
        //設(shè)置背景透明
        setBackgroundDrawable(new ColorDrawable());
        /*          //監(jiān)聽窗口的焦點(diǎn)事件,點(diǎn)擊窗口外面則取消顯示*/
        getContentView().setOnFocusChangeListener(new View.OnFocusChangeListener() {

            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (!hasFocus) {
                    dismiss();
                }
            }
        });
        initView(mainView);
    }

    @Override
    public void onClick(View view) {
        dismiss();
        if (view.getId() == R.id.pop_tv_customer_edit) {
            if(builder.popTopOnClick != null){
                builder.popTopOnClick.EditOnclick();
            }
        } else if (view.getId() == R.id.pop_tv_customer_del) {
            if(builder.popTopOnClick != null){
                builder.popTopOnClick.hashCode();
            }
        } else if (view.getId() == R.id.pop_tv_customer_sys) {
            if(builder.popTopOnClick != null){
                builder.popTopOnClick.SysOnclick();
            }
        } else if (view.getId() == R.id.pop_tv_customer_create) {
            if(builder.popTopOnClick != null){
                builder.popTopOnClick.CreateOnclick();
            }
        }
    }

    private void initView(View rootView) {
        popTvCustomerEdit = (TextView) rootView.findViewById(R.id.pop_tv_customer_edit);
        popTvCustomerEdit.setOnClickListener(PopTop.this);
        popTvCustomerDel = (TextView) rootView.findViewById(R.id.pop_tv_customer_del);
        popTvCustomerDel.setOnClickListener(PopTop.this);
        popLlEdit = (LinearLayout) rootView.findViewById(R.id.pop_ll_edit);
        popTvCustomerSys = (TextView) rootView.findViewById(R.id.pop_tv_customer_sys);
        popTvCustomerSys.setOnClickListener(PopTop.this);
        popTvCustomerCreate = (TextView) rootView.findViewById(R.id.pop_tv_customer_create);
        popTvCustomerCreate.setOnClickListener(PopTop.this);
        popLlCustomer = (LinearLayout) rootView.findViewById(R.id.pop_ll_customer);

        popLlEdit.setVisibility(View.GONE);
        popLlCustomer.setVisibility(View.GONE);

        

    }

    public static class Builder {
        protected final Context context;
        protected View view; 
        PopTopOnClick popTopOnClick;

        public Builder setPopTopOnClick(PopTopOnClick popTopOnClick) {
            this.popTopOnClick = popTopOnClick;
            return this;
        }

        
        public Builder setView(View view) {
            this.view = view;
            return this;
        }

        public Builder(@NonNull Context context) {
            this.context = context;
        }

        @UiThread
        public PopupWindow build() {
            return new PopTop(this);
        }

        @UiThread
        public PopupWindow show() {
            PopupWindow popTop = build();
            int windowPos[] = calculatePopWindowPos(view);
            windowPos[0] -= 15; //x 軸向左偏移15像素
            windowPos[1] -= 10; //y 軸向上偏移10像素
            popTop.showAtLocation(view, Gravity.TOP | Gravity.START, windowPos[0], windowPos[1]);
            return popTop;
        }
    }

    public int dip2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    /**
     * 計(jì)算出來的位置迟螺,y方向就在anchorView的上面和下面對齊顯示冲秽,x方向就是與屏幕右邊對齊顯示
     * 如果anchorView的位置有變化,就可以適當(dāng)自己額外加入偏移來修正
     *
     * @param anchorView 呼出window的view
     * @return window顯示的左上角的xOff, yOff坐標(biāo)
     */
    public static int[] calculatePopWindowPos(final View anchorView) {

        final int windowPos[] = new int[2];
        final int anchorLoc[] = new int[2];
        // 獲取錨點(diǎn)View在屏幕上的左上角坐標(biāo)位置
        anchorView.getLocationOnScreen(anchorLoc);
        final int anchorHeight = anchorView.getHeight();
        // 獲取屏幕的高寬
        final int screenHeight = getScreenHeight(anchorView.getContext());
        final int screenWidth = getScreenWidth(anchorView.getContext());
        mainView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
        // 計(jì)算contentView的高寬
        final int windowHeight = mainView.getMeasuredHeight();
        final int windowWidth = mainView.getMeasuredWidth();
        // 判斷需要向上彈出還是向下彈出顯示
        final boolean isNeedShowUp = (screenHeight - anchorLoc[1] - anchorHeight < windowHeight);
        if (isNeedShowUp) {
            windowPos[0] = screenWidth - windowWidth;
            windowPos[1] = anchorLoc[1] - windowHeight;
        } else {
            windowPos[0] = screenWidth - windowWidth;
            windowPos[1] = anchorLoc[1] + anchorHeight;
        }
        return windowPos;
    }

    /**
     * 獲取屏幕高度(px)
     */
    public static int getScreenHeight(Context context) {
        return context.getResources().getDisplayMetrics().heightPixels;
    }

    /**
     * 獲取屏幕寬度(px)
     */
    public static int getScreenWidth(Context context) {
        return context.getResources().getDisplayMetrics().widthPixels;
    }

    public interface PopTopOnClick{
        void EditOnclick();
        void DelOnclick();
        void SysOnclick();
        void CreateOnclick();
    }

}

最終你可以

if (view.getId() == R.id.common_tv_right_more) { 
        new PopTop.Builder(this)
                .setView(findViewById(R.id.common_tv_right_more)) // 在某個(gè)空間的下面 
                .setPopTopOnClick(new PopTop.PopTopOnClick() {
                    @Override
                    public void EditOnclick() {
                        /* 編輯 監(jiān)聽*/ 
                    } 
                    @Override
                    public void DelOnclick() {
                        /*刪除 監(jiān)聽*/
                    } 
                })
                .show();
    }

最后 同類想法

你可以寫一個(gè)評論的pop
例如


點(diǎn)擊彈出鍵盤 pop 顯示處理矩父,鍵盤消失pop 消失锉桑。
這個(gè)之前也有個(gè)項(xiàng)目里面有,但是沒有這么寫窍株,就堆在一起民轴,也沒有反思,那個(gè)時(shí)候還沒有blog球订,現(xiàn)在想一想真的太像了后裸。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市冒滩,隨后出現(xiàn)的幾起案子微驶,更是在濱河造成了極大的恐慌,老刑警劉巖开睡,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祈搜,死亡現(xiàn)場離奇詭異,居然都是意外死亡士八,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門梁呈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來婚度,“玉大人,你說我怎么就攤上這事』茸拢” “怎么了醋虏?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長哮翘。 經(jīng)常有香客問我颈嚼,道長,這世上最難降的妖魔是什么饭寺? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任阻课,我火速辦了婚禮,結(jié)果婚禮上艰匙,老公的妹妹穿的比我還像新娘限煞。我一直安慰自己,他們只是感情好员凝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布署驻。 她就那樣靜靜地躺著,像睡著了一般健霹。 火紅的嫁衣襯著肌膚如雪旺上。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天糖埋,我揣著相機(jī)與錄音宣吱,去河邊找鬼。 笑死阶捆,一個(gè)胖子當(dāng)著我的面吹牛凌节,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播洒试,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼倍奢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了垒棋?” 一聲冷哼從身側(cè)響起卒煞,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎叼架,沒想到半個(gè)月后畔裕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乖订,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年扮饶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乍构。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡甜无,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情岂丘,我是刑警寧澤陵究,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站奥帘,受9級特大地震影響铜邮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜寨蹋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一松蒜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧钥庇,春花似錦牍鞠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至吐句,卻和暖如春胁后,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嗦枢。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工攀芯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人文虏。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓侣诺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親氧秘。 傳聞我的和親對象是個(gè)殘疾皇子年鸳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

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