封装一个通用的PopupWindow

上篇文章是关于建造者设计模式的,今天顺便封装一个通用的 PopupWindow 来实践一下, 同时也方便之后使用 PopupWindow,本文将从下面几个方面来介绍 PopupWindow 及其封装,具体以下:java

  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 的经常使用属性设置,具体以下:ide

//设置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.gif

封装 PopupWindow

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

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

//获取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 的默认配置,具体以下:this

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 (contentView != null && layoutId != -1){
            throw new MException("setContentView and setLayoutId can't be used together.", "0");
        }else if (contentView == null && layoutId == -1){
            throw new MException("contentView or layoutId can't be null.", "1");
        }

        if (context == null) {
            throw new MException("context can't be null.", "2");
        } else {
            window.mContext = this.context;
        }

        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.mGravity = this.gravity;
    }
}
复制代码

显然,这里能够看出 context 和 contentView 或 layoutId 是必须须要设置的,若是没有设置相应的会有错误提示,固然在封装中也对 contentView 和 layoutId 不能同时使用作了限制和若是使用了二者的错误提示。

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

public void showPopupWindow(View v, LocationType type) {
    if (mView!=null){
        mPopupWindow.setContentView(mView);
    }else if (mLayoutId != -1){
        View contentView = LayoutInflater.from(mContext).inflate(mLayoutId, null);
        mPopupWindow.setContentView(contentView);
    }
    mPopupWindow.setWidth(mWidth);
    mPopupWindow.setHeight(mHeight);
    mPopupWindow.setBackgroundDrawable(mBackgroundDrawable);
    mPopupWindow.setOutsideTouchable(mOutsideTouchable);
    mPopupWindow.setOnDismissListener(mOnDismissListener);
    mPopupWindow.setAnimationStyle(mAnimationStyle);
    mPopupWindow.setTouchable(mTouchable);
    //获取目标View的坐标
    int[] locations = new int[2];
    v.getLocationOnScreen(locations);
    int left = locations[0];
    int top  =  locations[1];
    //获取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();

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

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

        case LEFT_TOP:
            mPopupWindow.showAtLocation(v, Gravity.NO_GRAVITY, left - popupWidth + mOffsetX, top - popupHeight + mOffsetY);
            break;
        case LEFT_BOTTOM:
            mPopupWindow.showAtLocation(v, Gravity.NO_GRAVITY, left - popupWidth + mOffsetX, top + v.getHeight() + mOffsetY);
            break;
        case LEFT_CENTER:
            int offsetY = (v.getHeight() - popupHeight) / 2;
            mPopupWindow.showAtLocation(v, Gravity.NO_GRAVITY,left - popupWidth + mOffsetX,top + offsetY + mOffsetY);
            break;

        case RIGHT_TOP:
            mPopupWindow.showAtLocation(v, Gravity.NO_GRAVITY, left + v.getWidth() + mOffsetX,top - popupHeight + mOffsetY);
            break;
        case RIGHT_BOTTOM:
            mPopupWindow.showAtLocation(v, Gravity.NO_GRAVITY, left + v.getWidth() + mOffsetX,top + v.getHeight() + mOffsetY);
            break;
        case RIGHT_CENTER:
            int offsetY1 = (v.getHeight() - popupHeight) / 2;
            mPopupWindow.showAtLocation(v, Gravity.NO_GRAVITY,left + v.getWidth() + mOffsetX,top + offsetY1 + mOffsetY);
            break;
        case FROM_BOTTOM:
            mPopupWindow.showAtLocation(v,mGravity,mOffsetX,mOffsetY);
            break;
    }
}
复制代码

使用封装后的PopupWindow

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

private void showPopupWindow(MPopupWindow.LocationType type) {
    MPopupWindow popupWindow = new MPopupWindow
            .Builder(this)
            .setLayoutId(R.layout.popup_window_layout)
            .build();
    popupWindow.showPopupWindow(btnTarget, type);
}
复制代码

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

显示效果:

下面是 PopupWindow 在各个位置的显示,具体以下:

MPopupWindow.gif

能够选择关注微信公众号:jzman-blog 获取最新更新,一块儿交流学习,公众号回复 MPopupWindow 获取源码连接。

零点小筑
相关文章
相关标签/搜索