*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
先上效果图:
完整代码地址已上传Github:CommonPopupWindow
PopupWindow这个类用来实现一个弹出框,可以使用任意布局的View作为其内容,这个弹出框是悬浮在当前activity之上的。
一般PopupWindow的使用:
//准备PopupWindow的布局View
View popupView = LayoutInflater.from(this).inflate(R.layout.popup, null);
//初始化一个PopupWindow,width和height都是WRAP_CONTENT
PopupWindow popupWindow = new PopupWindow(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
//设置PopupWindow的视图内容
popupWindow.setContentView(popupView);
//点击空白区域PopupWindow消失,这里必须先设置setBackgroundDrawable,否则点击无反应
popupWindow.setBackgroundDrawable(new ColorDrawable(0x00000000));
popupWindow.setOutsideTouchable(true);
//设置PopupWindow动画
popupWindow.setAnimationStyle(R.style.AnimDown);
//设置是否允许PopupWindow的范围超过屏幕范围
popupWindow.setClippingEnabled(true);
//设置PopupWindow消失监听
popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
}
});
//PopupWindow在targetView下方弹出
popupWindow.showAsDropDown(targetView);
上面就是PopupWindow通常需要设置的各个方法,不难,但是稍微有点繁琐,有些是可以复用的,所以封装了一个通用的CommonPopupWindow:
CommonPopupWindow继承自PopupWindow,拥有PopupWindow的各个属性方法,使用类似建造者模式,和AlertDialog的使用方式差不多,CommonPopupWindow使用举例:
CommonPopupWindow popupWindow = new CommonPopupWindow.Builder(this)
//设置PopupWindow布局
.setView(R.layout.popup_down)
//设置宽高
.setWidthAndHeight(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
//设置动画
.setAnimationStyle(R.style.AnimDown)
//设置背景颜色,取值范围0.0f-1.0f 值越小越暗 1.0f为透明
.setBackGroundLevel(0.5f)
//设置PopupWindow里的子View及点击事件
.setViewOnclickListener(new CommonPopupWindow.ViewInterface() {
@Override
public void getChildView(View view, int layoutResId) {
TextView tv_child = (TextView) view.findViewById(R.id.tv_child);
tv_child.setText("我是子View");
}
})
//设置外部是否可点击 默认是true
.setOutsideTouchable(true)
//开始构建
.create();
//弹出PopupWindow
popupWindow.showAsDropDown(view);
- CommonPopupWindow 设置背景:
这里使用的是WindowManager.LayoutParams.alpha属性,看下官网解释:An alpha value to apply to this entire window. An alpha of 1.0 means fully opaque and 0.0 means fully transparent .alpha 值适用于整个Window,α为1.0时表示完全不透明而0.0表示完全透明,默认是1.0,当PopupWindow弹出时通过设置alpha在(0.0,1.0)之间设置灰色背景,当PopupWindow消失时恢复默认值。
private void setBackGroundLevel(float level) {
mWindow = ((Activity) context).getWindow();
WindowManager.LayoutParams params = mWindow.getAttributes();
params.alpha = level;
mWindow.setAttributes(params);
}
- 计算CommonPopupWindow 宽高:
//设置测量模式为UNSPECIFIED可以确保测量不受父View的影响
int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
view.measure(w, h);
//得到测量宽度
int mWidth=view.getMeasuredWidth();
//得到测量高度
int mHeight=view.getMeasuredHeight();
注:在测量宽高时遇到一种情况,如图所示:
如果设置TextView 的 android:layout_width="wrap_content",那么测量不出TextView 准确的height,当设置width为某个确定值时,也能得到准确的height了。
- CommonPopupWindow 设置动画:
如设置向右动画:
.setAnimationStyle(R.style.AnimHorizontal);
在style.xml文件中设置:
android:windowEnterAnimation、android:windowExitAnimation分别为Popupwindow弹出和消失动画
进入动画为anim目录下的 push_scale_left_in.xml:
消失动画为 push_scale_left_out.xml:
- CommonPopupWindow 弹出:
因为CommonPopupWindow继承自PopupWindow,所以可以直接使用PopupWindow中的弹出方法,常用的下面三种:
public void showAsDropDown(View anchor)
public void showAsDropDown(View anchor, int xoff, int yoff)
public void showAtLocation(View parent, int gravity, int x, int y)
其中,showAsDropDown是显示在参照物anchor的周围,xoff、yoff分别是X轴、Y轴的偏移量,如果不设置xoff、yoff,默认是显示在anchor的下方;showAtLocation是设置在父控件的位置,如设置Gravity.BOTTOM表示在父控件底部弹出,xoff、yoff也是X轴、Y轴的偏移量。
如上面向右弹出例子,分别使用showAsDropDown和showAtLocation来实现:
showAsDropDown:
popupWindow.showAsDropDown(view, view.getWidth(), -view.getHeight());
showAsDropDown默认展示在button的下面,通过改变X轴和Y轴的偏移量(X轴向右偏移button的宽度,Y轴向上偏移button的高度),实现在Button右边弹出。
showAtLocation:
int[] positions = new int[2];
view.getLocationOnScreen(positions);
popupWindow.showAtLocation(findViewById(android.R.id.content),
Gravity.START| Gravity.TOP , positions[0] + view.getWidth(), positions[1]);
使用了View的getLocationOnScreen方法来获得View在屏幕中的坐标位置,传入的参数必须是一个有2个整数的数组,分别代表View的X、Y坐标,即是View的左上角的坐标,这里的View是Button,知道了Button左上角的坐标,就可以得到要展示的PopupWindow的左上角的坐标为(positions[0] + view.getWidth(), positions[1]),从而实现在Button右边弹出。
最后贴一下代码 CommonPopupWindow.java:
public class CommonPopupWindow extends PopupWindow {
final PopupController controller;
@Override
public int getWidth() {
return controller.mPopupView.getMeasuredWidth();
}
@Override
public int getHeight() {
return controller.mPopupView.getMeasuredHeight();
}
public interface ViewInterface {
void getChildView(View view, int layoutResId);
}
private CommonPopupWindow(Context context) {
controller = new PopupController(context, this);
}
@Override
public void dismiss() {
super.dismiss();
controller.setBackGroundLevel(1.0f);
}
public static class Builder {
private final PopupController.PopupParams params;
private ViewInterface listener;
public Builder(Context context) {
params = new PopupController.PopupParams(context);
}
/**
* @param layoutResId 设置PopupWindow 布局ID
* @return Builder
*/
public Builder setView(int layoutResId) {
params.mView = null;
params.layoutResId = layoutResId;
return this;
}
/**
* @param view 设置PopupWindow布局
* @return Builder
*/
public Builder setView(View view) {
params.mView = view;
params.layoutResId = 0;
return this;
}
/**
* 设置子View
*
* @param listener ViewInterface
* @return Builder
*/
public Builder setViewOnclickListener(ViewInterface listener) {
this.listener = listener;
return this;
}
/**
* 设置宽度和高度 如果不设置 默认是wrap_content
*
* @param width 宽
* @return Builder
*/
public Builder setWidthAndHeight(int width, int height) {
params.mWidth = width;
params.mHeight = height;
return this;
}
/**
* 设置背景灰色程度
*
* @param level 0.0f-1.0f
* @return Builder
*/
public Builder setBackGroundLevel(float level) {
params.isShowBg = true;
params.bg_level = level;
return this;
}
/**
* 是否可点击Outside消失
*
* @param touchable 是否可点击
* @return Builder
*/
public Builder setOutsideTouchable(boolean touchable) {
params.isTouchable = touchable;
return this;
}
/**
* 设置动画
*
* @return Builder
*/
public Builder setAnimationStyle(int animationStyle) {
params.isShowAnim = true;
params.animationStyle = animationStyle;
return this;
}
public CommonPopupWindow create() {
final CommonPopupWindow popupWindow = new CommonPopupWindow(params.mContext);
params.apply(popupWindow.controller);
if (listener != null && params.layoutResId != 0) {
listener.getChildView(popupWindow.controller.mPopupView, params.layoutResId);
}
CommonUtil.measureWidthAndHeight(popupWindow.controller.mPopupView);
return popupWindow;
}
}
}
PopupController.java:
class PopupController {
private int layoutResId;//布局id
private Context context;
private PopupWindow popupWindow;
View mPopupView;//弹窗布局View
private View mView;
private Window mWindow;
PopupController(Context context, PopupWindow popupWindow) {
this.context = context;
this.popupWindow = popupWindow;
}
public void setView(int layoutResId) {
mView = null;
this.layoutResId = layoutResId;
installContent();
}
public void setView(View view) {
mView = view;
this.layoutResId = 0;
installContent();
}
private void installContent() {
if (layoutResId != 0) {
mPopupView = LayoutInflater.from(context).inflate(layoutResId, null);
} else if (mView != null) {
mPopupView = mView;
}
popupWindow.setContentView(mPopupView);
}
/**
* 设置宽度
*
* @param width 宽
* @param height 高
*/
private void setWidthAndHeight(int width, int height) {
if (width == 0 || height == 0) {
//如果没设置宽高,默认是WRAP_CONTENT
popupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
popupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
} else {
popupWindow.setWidth(width);
popupWindow.setHeight(height);
}
}
/**
* 设置背景灰色程度
*
* @param level 0.0f-1.0f
*/
void setBackGroundLevel(float level) {
mWindow = ((Activity) context).getWindow();
WindowManager.LayoutParams params = mWindow.getAttributes();
params.alpha = level;
mWindow.setAttributes(params);
}
/**
* 设置动画
*/
private void setAnimationStyle(int animationStyle) {
popupWindow.setAnimationStyle(animationStyle);
}
/**
* 设置Outside是否可点击
*
* @param touchable 是否可点击
*/
private void setOutsideTouchable(boolean touchable) {
popupWindow.setBackgroundDrawable(new ColorDrawable(0x00000000));//设置透明背景
popupWindow.setOutsideTouchable(touchable);//设置outside可点击
popupWindow.setFocusable(touchable);
}
static class PopupParams {
public int layoutResId;//布局id
public Context mContext;
public int mWidth, mHeight;//弹窗的宽和高
public boolean isShowBg, isShowAnim;
public float bg_level;//屏幕背景灰色程度
public int animationStyle;//动画Id
public View mView;
public boolean isTouchable = true;
public PopupParams(Context mContext) {
this.mContext = mContext;
}
public void apply(PopupController controller) {
if (mView != null) {
controller.setView(mView);
} else if (layoutResId != 0) {
controller.setView(layoutResId);
} else {
throw new IllegalArgumentException("PopupView's contentView is null");
}
controller.setWidthAndHeight(mWidth, mHeight);
controller.setOutsideTouchable(isTouchable);//设置outside可点击
if (isShowBg) {
//设置背景
controller.setBackGroundLevel(bg_level);
}
if (isShowAnim) {
controller.setAnimationStyle(animationStyle);
}
}
}
}