上篇文章是关于建造者设计模式的,今天顺便封装一个通用的 PopupWindow 来实践一下, 同时也方便之后使用 PopupWindow,本文将从下面几个方面来介绍 PopupWindow 及其封装,具体以下:java
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 的封装主要是对 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 了,具体以下:
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 在各个位置的显示,具体以下:
能够选择关注微信公众号:jzman-blog 获取最新更新,一块儿交流学习,公众号回复 MPopupWindow 获取源码连接。