一句话搞定PopupWindow

前言

设计图给出的效果


最终的效果


最终代码
common_tv_right_more 的点击事件

    if (view.getId() == R.id.common_tv_right_more) { 
        new PopTop.Builder(this)
                .setView(findViewById(R.id.common_tv_right_more)) // 在某个空间的下面 
                .setPopTopOnClick(new PopTop.PopTopOnClick() {
                    @Override
                    public void EditOnclick() {
                        /* 编辑 监听*/ 
                    } 
                    @Override
                    public void DelOnclick() {
                        /*删除 监听*/
                    } 
                })
                .show();
    }

接着开始想和学习怎么做了。

使用场景

PopupWindow,顾名思义,就是弹窗,在很多场景下都可以见到它。例如ActionBar/Toolbar的选项弹窗,一组选项的容器,或者列表等集合的窗口等等。

基本使用

使用PopupWindow很简单,可以总结为三个步骤:

1、创建PopupWindow对象实例;

2、设置背景、注册事件监听器和添加动画;

3、显示PopupWindow。

// 用于PopupWindow的View
View contentView=LayoutInflater.from(context).inflate(layoutRes, null, false);
// 创建PopupWindow对象,其中:
// 第一个参数是用于PopupWindow中的View,第二个参数是PopupWindow的宽度,
// 第三个参数是PopupWindow的高度,第四个参数指定PopupWindow能否获得焦点
PopupWindow window=new PopupWindow(contentView, 100, 100, true);
// 设置PopupWindow的背景
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
// 设置PopupWindow是否能响应外部点击事件
window.setOutsideTouchable(true);
// 设置PopupWindow是否能响应点击事件
window.setTouchable(true);
// 显示PopupWindow,其中:
// 第一个参数是PopupWindow的锚点,第二和第三个参数分别是PopupWindow相对锚点的x、y偏移
window.showAsDropDown(anchor, xoff, yoff);
// 或者也可以调用此方法显示PopupWindow,其中:
// 第一个参数是PopupWindow的父View,第二个参数是PopupWindow相对父View的位置,
// 第三和第四个参数分别是PopupWindow相对父View的x、y偏移
// window.showAtLocation(parent, gravity, x, y);

使用showAsDropDown方法显示PopupWindow

通常情况下,调用showAsDropDown方法后PopupWindow将会在锚点的左下方显示(drop down)。但是,有时想让PopupWindow在锚点的上方显示,或者在锚点的中间位置显示,此时就需要用到showAsDropDown方法的xoff和yoff参数了。

这里我们的目的不仅包括上面提到的两种情况(锚点上方或锚点中部),而是囊括了水平和垂直方向各5种显示方式:

  1. 水平方向:
    • ALIGN_LEFT:在锚点内部的左边;
    • ALIGN_RIGHT:在锚点内部的右边;
    • CENTER_HORI:在锚点水平中部;
    • TO_RIGHT:在锚点外部的右边;
    • TO_LEFT:在锚点外部的左边。
  1. 垂直方向:
    • ALIGN_ABOVE:在锚点内部的上方;
    • ALIGN_BOTTOM:在锚点内部的下方;
    • CENTER_VERT:在锚点垂直中部;
    • TO_BOTTOM:在锚点外部的下方;
    • TO_ABOVE:在锚点外部的上方。

下面来看张图:


showAsDropDown 可以做哪些效果

想要的效果 是在某个控件的下面 并且还要有偏移 角度,所以在偏移角度上
大体是多少再去调试。
所以在主要的方法落在了showAsDropDown 的下面。 想要写成通用的也不是不可以的,先不要想这么多,先去做,实现目前的效果再说。

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

这个view 的产出,所以就可以把它单独拿起来,在单独页面中和xml 处理,底色背景和角度了,
以及整个view 中的点击事件处理,并且显得不那么啰嗦了,看简单的代码,就用到build设计模式,
突出了这个模式的优点,不断添加 各个设置条件。

build设计模式

自己之前也写过关于设计模式的问题。

首先看是build 的时候创建view 或者说产出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));
    //设置宽度
    setWidth(dip2px(builder.context, 100));
    //设置高度
    setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
    setTouchable(true);
    setFocusable(true);
    //设置显示隐藏动画
    //        setAnimationStyle(R.style.AnimTools);
    //设置背景透明
    setBackgroundDrawable(new ColorDrawable());
    /*          //监听窗口的焦点事件,点击窗口外面则取消显示*/
    getContentView().setOnFocusChangeListener(new View.OnFocusChangeListener() {

        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            if (!hasFocus) {
                dismiss();
            }
        }
    });
    //初始化 控件点击事件处理
    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 中

对偏移量的处理,并且在合适的位置处理,在上面基本中说道。

/**
 * 计算出来的位置,y方向就在anchorView的上面和下面对齐显示,x方向就是与屏幕右边对齐显示
 * 如果anchorView的位置有变化,就可以适当自己额外加入偏移来修正
 *
 * @param anchorView 呼出window的view
 * @return window显示的左上角的xOff, yOff坐标
 */
public static int[] calculatePopWindowPos(final View anchorView) {

    final int windowPos[] = new int[2];
    final int anchorLoc[] = new int[2];
    // 获取锚点View在屏幕上的左上角坐标位置
    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);
    // 计算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;
}

大体流程就是这些了,至于点击事件,进行事件监听,也是从产生的pop的时候传递过来的。

所有代码

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));
        //设置宽度
        setWidth(dip2px(builder.context, 100));
        //设置高度
        setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
        setTouchable(true);
        setFocusable(true);
        //设置显示隐藏动画
//        setAnimationStyle(R.style.AnimTools);
        //设置背景透明
        setBackgroundDrawable(new ColorDrawable());
        /*          //监听窗口的焦点事件,点击窗口外面则取消显示*/
        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);
    }

    /**
     * 计算出来的位置,y方向就在anchorView的上面和下面对齐显示,x方向就是与屏幕右边对齐显示
     * 如果anchorView的位置有变化,就可以适当自己额外加入偏移来修正
     *
     * @param anchorView 呼出window的view
     * @return window显示的左上角的xOff, yOff坐标
     */
    public static int[] calculatePopWindowPos(final View anchorView) {

        final int windowPos[] = new int[2];
        final int anchorLoc[] = new int[2];
        // 获取锚点View在屏幕上的左上角坐标位置
        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);
        // 计算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)) // 在某个空间的下面 
                .setPopTopOnClick(new PopTop.PopTopOnClick() {
                    @Override
                    public void EditOnclick() {
                        /* 编辑 监听*/ 
                    } 
                    @Override
                    public void DelOnclick() {
                        /*删除 监听*/
                    } 
                })
                .show();
    }

最后 同类想法

你可以写一个评论的pop
例如


点击弹出键盘 pop 显示处理,键盘消失pop 消失。
这个之前也有个项目里面有,但是没有这么写,就堆在一起,也没有反思,那个时候还没有blog,现在想一想真的太像了。

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

推荐阅读更多精彩内容