先上效果图:
完整代码地址已上传Github:CommonPopupWindow
PopupWindow这个类用来实现一个弹出框,可以使用任意布局的View作为其内容,这个弹出框是悬浮在当前activity之上的。
一般PopupWindow的使用:
//准备PopupWindow的布局ViewView popupView = LayoutInflater.from(this).inflate(R.layout.popup, null);//初始化一个PopupWindow,width和height都是WRAP_CONTENTPopupWindow 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();//弹出PopupWindowpopupWindow.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文件中设置:
<style name="AnimHorizontal" parent="@android:style/Animation"> <item name="android:windowEnterAnimation">@anim/push_scale_left_in</item> <item name="android:windowExitAnimation">@anim/push_scale_left_out</item> </style>
android:windowEnterAnimation、android:windowExitAnimation分别为Popupwindow弹出和消失动画
进入动画为anim目录下的 push_scale_left_in.xml:
<scale xmlns:android="http://schemas.android.com/apk/res/android" android:duration="200" android:fromXScale="0.0" android:fromYScale="1.0" android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:toXScale="1.0" android:toYScale="1.0" />
消失动画为 push_scale_left_out.xml:
<scale xmlns:android="http://schemas.android.com/apk/res/android" android:duration="200" android:fromXScale="1.0" android:fromYScale="1.0" android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:toXScale="0.0" android:toYScale="1.0" />
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); } } } }