封装一个通用的PopupWindow

上篇文章是关于建造者设计模式的,今天顺便封装一个通用的 PopupWindow 来实践一下, 同时也方便以后使用 PopupWindow,如果需要可以直接引入使用:

implementation 'com.github.jzmanu:MPopupWindow:v1.0.0'

本文将从下面几个方面来介绍 PopupWindow 及其封装,具体如下:

  1. 概述
  2. 常用方法
  3. 基本使用
  4. 封装 PopupWindow
  5. 使用封装后的PopupWindow
  6. 显示效果

概述

PopupWindow 表示一个弹窗,类似于 AlertDialog,相较 AlertDialog 来说 PopupWindow 使用起来更灵活,可有任意指定要显示的位置,当然能够灵活的使用必然在某一层面有所牺牲,如 PopupWindow 相较 AlertDialog 没有默认的布局,每次都得专门创建弹窗的布局,这一点来说 AlertDialog 就比较方便了,所以在开发中没有最好的解决方案,要根据具体的需求选择最合适的解决方案。

常用设置

PopupWindow 的创建,具体如下:

 //构造方法
 public PopupWindow (Context context)  
 public PopupWindow(View contentView)  
 public PopupWindow(View contentView, int width, int height)  
 public PopupWindow(View contentView, int width, int height, boolean focusable) 

PopupWindow 的常用属性设置,具体如下:

 //设置View(必须)
 window.setContentView(contentView);
 //设置宽(必须)
 window.setWidth(WindowManager.LayoutParams.MATCH_PARENT);
 //设置高(必须)
 window.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
 //设置背景
 window.setBackgroundDrawable(new ColorDrawable(Color.GRAY));
 //设置PopupWindow之外的触摸事件
 window.setOutsideTouchable(true);
 //设置PopupWindow消失的监听器
 window.setOnDismissListener(this);
 //设置PopupWindow上的触摸事件
 window.setTouchable(true);
 //设置PopupWindow弹出动画
 window.setAnimationStyle(R.style.PopupWindowTranslateTheme);

PopupWindow 的显示有两种设置方式,一种是基于坐标,另一种是基于某个 View ,具体如下:

//基于坐标,参数(当前窗口的某个 View,位置,起始坐标x, 起始坐标y)
void showAtLocation (View parent, int gravity, int x, int y)
//基于某个View,参数(附着的View,x 方向的偏移量,y 方向的偏移量)
void showAsDropDown (View anchor, int xoff, int yoff, int gravity) 
void showAsDropDown (View anchor, int xoff, int yoff)
void showAsDropDown (View anchor) 

基本使用

PopupWindow 的主要内容基本如上,下面使用原生的 PopupWindow 实现一个弹窗,下面是关键代码,具体如下:

//创建PopupWindow
PopupWindow window = new PopupWindow(this);
//设置显示View
window.setContentView(contentView);
//设置宽高
window.setWidth(WindowManager.LayoutParams.MATCH_PARENT);
window.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
//设置背景
window.setBackgroundDrawable(new ColorDrawable(Color.GRAY));
//设置PopupWindow之外的触摸事件
window.setOutsideTouchable(true);
//设置PopupWindow消失的监听器
window.setOnDismissListener(new PopupWindow.OnDismissListener() {
    @Override
    public void onDismiss() {
        //监听PopupWindow的消失
    }
});
//设置PopupWindow上的触摸事件
window.setTouchable(true);
//设置PopupWindow弹出动画
window.setAnimationStyle(R.style.PopupWindowTranslateTheme);
window.showAtLocation(btnTarget, Gravity.BOTTOM | Gravity.CENTER, 0, 0);

封装 PopupWindow

这里对 PopupWindow 的封装主要是对 PopupWindow 常用摆放位置做进一步封装,使 PopupWindow 的调用更加灵活、简洁。

在封装过程中遇到的问题是不能正确获取到 PopupWindow 的宽高,正确获取宽高的方法是先对 PopupWindow 进行测量,然后再获取其宽高,具体如下:

//获取PopupWindow的宽高
mPopupWindow.getContentView().measure(
    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
int popupWidth = mPopupWindow.getContentView().getMeasuredWidth();
int popupHeight = mPopupWindow.getContentView().getMeasuredHeight();

对 PopupWindow 的封装使用了建造者设计模式,下面看一下 PopupWindow 的默认配置,具体如下:

public Builder(Context context) {
    this.context = context;
    this.popupWindow = new PopupWindow(context);
    //默认PopupWindow响应触摸事件
    this.outsideTouchable = true;
    //默认响应触摸事件
    this.touchable = true;
    //默认背景透明
    this.backgroundDrawable = new ColorDrawable(Color.TRANSPARENT);
    //默认宽高为WRAP_CONTENT
    this.width  = WindowManager.LayoutParams.WRAP_CONTENT;
    this.height = WindowManager.LayoutParams.WRAP_CONTENT;
    //默认Gravity为Gravity.CENTER
    this.gravity = Gravity.CENTER;
    this.layoutId = -1;
    //默认偏移量为0
    this.offsetX = 0;
    this.offsetY = 0;
    //...
}

由于宽高、背景、是否可点击等相关属性已经设置了默认值,使用时根据自己的需求设置相关属性,如 PopupWindow 的动画等,所以这些设置肯定是非必须的,那么那些事创建时必须的呢。

下面是对 PopupWindow 封装类 MPopupWindow 的初始化,具体如下:

private void setPopupWindowConfig(MPopupWindow window) {
    if (context == null) {
        throw new MException("context can't be null.");
    } else {
        window.mContext = this.context;
    }

    if (contentView != null && layoutId != -1) {
        throw new MException("setContentView and setLayoutId can't be used together.");
    } else if (contentView == null && layoutId == -1) {
        throw new MException("contentView or layoutId can't be null.");
    }

    if (target == null){
        throw new MException("please set a target view");
    }

    window.mWidth = this.width;
    window.mHeight = this.height;
    window.mView = this.contentView;
    window.mLayoutId = layoutId;
    window.mPopupWindow = this.popupWindow;
    window.mOutsideTouchable = this.outsideTouchable;
    window.mBackgroundDrawable = this.backgroundDrawable;
    window.mOnDismissListener = this.onDismissListener;
    window.mAnimationStyle = this.animationStyle;
    window.mTouchable = this.touchable;
    window.mOffsetX = this.offsetX;
    window.mOffsetY = this.offsetY;
    window.mTarget = this.target;
    window.mGravity = this.gravity;
}

显然,这里可以看出 context 和 contentView 或 layoutId 是必须需要设置的,如果没有设置相应的会有错误提示,当然在封装中也对 contentView 和 layoutId 不能同时使用做了限制和如果使用了两者的错误提示。

下面是对外提供的显示 PopupWindow 的方法,根据不同的枚举类型将 PopupWindow 显示在不同的位置,具体如下:

public void show() {
    if (mView != null) {
        mPopupWindow.setContentView(mView);
    } else if (mLayoutId != -1) {
        View contentView = LayoutInflater.from(mContext).inflate(mLayoutId, null);
        mPopupWindow.setContentView(contentView);
    }
    if (mWidth != 0) mPopupWindow.setWidth(mWidth);
    if (mHeight != 0) mPopupWindow.setHeight(mHeight);
    mPopupWindow.setBackgroundDrawable(mBackgroundDrawable);
    mPopupWindow.setOutsideTouchable(mOutsideTouchable);
    mPopupWindow.setOnDismissListener(mOnDismissListener);
    mPopupWindow.setAnimationStyle(mAnimationStyle);
    mPopupWindow.setTouchable(mTouchable);

    int[] locations = new int[2];
    mTarget.getLocationOnScreen(locations);
    int left = locations[0];
    int top = locations[1];

    mPopupWindow.getContentView().measure(
            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
    int popupWidth = mPopupWindow.getContentView().getMeasuredWidth();
    int popupHeight = mPopupWindow.getContentView().getMeasuredHeight();
    int targetWidth = mTarget.getWidth();
    int targetHeight = mTarget.getHeight();

    switch (mGravity) {
        case TypeGravity.TOP_LEFT:
            mPopupWindow.showAtLocation(mTarget, Gravity.NO_GRAVITY, left + mOffsetX, top - popupHeight + mOffsetY);
            break;
        case TypeGravity.TOP_CENTER:
            int offsetX = (targetWidth - popupWidth) / 2;
            mPopupWindow.showAtLocation(mTarget, Gravity.NO_GRAVITY, left + offsetX + mOffsetX, top - popupHeight + mOffsetY);
            break;
        case TypeGravity.TOP_RIGHT:
            mPopupWindow.showAtLocation(mTarget, Gravity.NO_GRAVITY, left + targetWidth - popupWidth + mOffsetX, top - popupHeight + mOffsetY);
            break;

        case TypeGravity.CENTER:
            int x = left + (targetWidth - popupWidth) / 2 + mOffsetX;
            int y = top + (targetHeight - popupHeight) / 2 + mOffsetY;
            mPopupWindow.showAtLocation(mTarget, Gravity.NO_GRAVITY, x, y);
            break;
        case TypeGravity.CENTER_LEFT_TOP:
            mPopupWindow.showAtLocation(mTarget, Gravity.NO_GRAVITY, left + mOffsetX, top + mOffsetY);
            break;
        case TypeGravity.CENTER_LEFT_BOTTOM:
            mPopupWindow.showAtLocation(mTarget, Gravity.NO_GRAVITY, left + mOffsetX, top + (targetWidth - popupHeight) + mOffsetY);
            break;
        case TypeGravity.CENTER_RIGHT_BOTTOM:
            mPopupWindow.showAtLocation(mTarget, Gravity.NO_GRAVITY, left + (targetWidth - popupWidth) + mOffsetX, top + (targetHeight - popupHeight) + mOffsetY);
            break;
        case TypeGravity.CENTER_RIGHT_TOP:
            mPopupWindow.showAtLocation(mTarget, Gravity.NO_GRAVITY, left + (targetWidth - popupWidth) + mOffsetX, top + mOffsetY);
            break;

        case TypeGravity.BOTTOM_LEFT:
            mPopupWindow.showAsDropDown(mTarget, mOffsetX, mOffsetY);
            break;
        case TypeGravity.BOTTOM_CENTER:
            int offsetX1 = (targetWidth - popupWidth) / 2;
            mPopupWindow.showAsDropDown(mTarget, offsetX1 + mOffsetX, mOffsetY);
            break;
        case TypeGravity.BOTTOM_RIGHT:
            mPopupWindow.showAsDropDown(mTarget, targetWidth - popupWidth + mOffsetX, mOffsetY);
            break;

        case TypeGravity.FROM_BOTTOM:
            if (mWidth == 0) mPopupWindow.setWidth(WindowManager.LayoutParams.MATCH_PARENT);
            mPopupWindow.showAtLocation(mTarget, Gravity.BOTTOM, mOffsetX, mOffsetY);
            break;
        case TypeGravity.FROM_TOP:
            if (mWidth == 0) mPopupWindow.setWidth(WindowManager.LayoutParams.MATCH_PARENT);
            mPopupWindow.showAtLocation(mTarget, Gravity.TOP, mOffsetX, mOffsetY);
            break;
    }
}

使用封装后的PopupWindow

下面是使用封装后的 PopupWindow,只需几行行代码就可以显示一个默认的 PopupWindow 了,具体如下:

MPopupWindow.create(this)
      .setLayoutId(R.layout.popup_window_layout)
      .setTarget(mTarget)
      .setGravity(gravity)
      .build()
      .show()

由于默认 PopupWindow 背景是透明的,建议测试时设置背景。

显示效果

下面是 PopupWindow 在各个位置的显示,还可以在此基础通过设置偏移量实现任意位置的显示,显示效果如下:

mpopupwinow.gif

欢迎star

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

推荐阅读更多精彩内容