封装一个通用的PopupWindow
上篇文章是关于建造者设计模式的,今天顺便封装一个通用的 PopupWindow 来实践一下, 同时也方便以后使用 PopupWindow,本文将从下面几个方面来介绍 PopupWindow 及其封装,具体如下:
概述
常用方法
基本使用
封装 PopupWindow
使用封装后的PopupWindow
显示效果
概述
PopupWindow 表示一个弹窗,类似于 AlertDialog,相较 AlertDialog 来说 PopupWindow 使用起来更灵活,可有任意指定要显示的位置,当然能够灵活的使用必然在某一层面有所牺牲,如 PopupWindow 相较 AlertDialog 没有默认的布局,每次都得专门创建弹窗的布局,这一点来说 AlertDialog 就比较方便了,所以在开发中没有最好的解决方案,要根据具体的需求选择最合适的解决方案。
常用设置
PopupWindow 的创建,具体如下:
1 //构造方法
2 public PopupWindow (Context context)
3 public PopupWindow(View contentView)
4 public PopupWindow(View contentView, int width, int height)
5 public PopupWindow(View contentView, int width, int height, boolean focusable)
6
PopupWindow 的常用属性设置,具体如下:
1 //设置View(必须)
2 window.setContentView(contentView);
3 //设置宽(必须)
4 window.setWidth(WindowManager.LayoutParams.MATCH_PARENT);
5 //设置高(必须)
6 window.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
7 //设置背景
8 window.setBackgroundDrawable(new ColorDrawable(Color.GRAY));
9 //设置PopupWindow之外的触摸事件
10 window.setOutsideTouchable(true);
11 //设置PopupWindow消失的监听器
12 window.setOnDismissListener(this);
13 //设置PopupWindow上的触摸事件
14 window.setTouchable(true);
15 //设置PopupWindow弹出动画
16 window.setAnimationStyle(R.style.PopupWindowTranslateTheme);
PopupWindow 的显示有两种设置方式,一种是基于坐标,另一种是基于某个 View ,具体如下:
1//基于坐标,参数(当前窗口的某个 View,位置,起始坐标x, 起始坐标y)
2void showAtLocation (View parent, int gravity, int x, int y)
3//基于某个View,参数(附着的View,x 方向的偏移量,y 方向的偏移量)
4void showAsDropDown (View anchor, int xoff, int yoff, int gravity)
5void showAsDropDown (View anchor, int xoff, int yoff)
6void showAsDropDown (View anchor)
7
基本使用
PopupWindow 的主要内容基本如上,下面使用原生的 PopupWindow 实现一个弹窗,下面是关键代码,具体如下:
1//创建PopupWindow
2PopupWindow window = new PopupWindow(this);
3//设置显示View
4window.setContentView(contentView);
5//设置宽高
6window.setWidth(WindowManager.LayoutParams.MATCH_PARENT);
7window.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
8//设置背景
9window.setBackgroundDrawable(new ColorDrawable(Color.GRAY));
10//设置PopupWindow之外的触摸事件
11window.setOutsideTouchable(true);
12//设置PopupWindow消失的监听器
13window.setOnDismissListener(new PopupWindow.OnDismissListener() {
14
15 public void onDismiss() {
16 //监听PopupWindow的消失
17 }
18});
19//设置PopupWindow上的触摸事件
20window.setTouchable(true);
21//设置PopupWindow弹出动画
22window.setAnimationStyle(R.style.PopupWindowTranslateTheme);
23window.showAtLocation(btnTarget, Gravity.BOTTOM | Gravity.CENTER, 0, 0);
下面是显示效果,具体如下:
封装 PopupWindow
这里对 PopupWindow 的封装主要是对 PopupWindow 常用摆放位置做进一步封装,使 PopupWindow 的调用更加灵活、简洁。
在封装过程中遇到的问题是不能正确获取到 PopupWindow 的宽高,正确获取宽高的方法是先对 PopupWindow 进行测量,然后再获取其宽高,具体如下:
1//获取PopupWindow的宽高
2mPopupWindow.getContentView().measure(
3 View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
4 View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
5int popupWidth = mPopupWindow.getContentView().getMeasuredWidth();
6int popupHeight = mPopupWindow.getContentView().getMeasuredHeight();
对 PopupWindow 的封装使用了建造者设计模式,下面看一下 PopupWindow 的默认配置,具体如下:
1public Builder(Context context) {
2 this.context = context;
3 this.popupWindow = new PopupWindow(context);
4 //默认PopupWindow响应触摸事件
5 this.outsideTouchable = true;
6 //默认响应触摸事件
7 this.touchable = true;
8 //默认背景透明
9 this.backgroundDrawable = new ColorDrawable(Color.TRANSPARENT);
10 //默认宽高为WRAP_CONTENT
11 this.width = WindowManager.LayoutParams.WRAP_CONTENT;
12 this.height = WindowManager.LayoutParams.WRAP_CONTENT;
13 //默认Gravity为Gravity.CENTER
14 this.gravity = Gravity.CENTER;
15 this.layoutId = -1;
16 //默认偏移量为0
17 this.offsetX = 0;
18 this.offsetY = 0;
19 //...
20}
由于宽高、背景、是否可点击等相关属性已经设置了默认值,使用时根据自己的需求设置相关属性,如 PopupWindow 的动画等,所以这些设置肯定是非必须的,那么那些事创建时必须的呢。下面是对 PopupWindow 封装类 MPopupWindow 的初始化,具体如下:
1private void setPopupWindowConfig(MPopupWindow window) {
2 if (contentView != null && layoutId != -1){
3 throw new MException("setContentView and setLayoutId can't be used together.", "0");
4 }else if (contentView == null && layoutId == -1){
5 throw new MException("contentView or layoutId can't be null.", "1");
6 }
7
8 if (context == null) {
9 throw new MException("context can't be null.", "2");
10 } else {
11 window.mContext = this.context;
12 }
13
14 window.mWidth = this.width;
15 window.mHeight = this.height;
16 window.mView = this.contentView;
17 window.mLayoutId = layoutId;
18 window.mPopupWindow = this.popupWindow;
19 window.mOutsideTouchable = this.outsideTouchable;
20 window.mBackgroundDrawable = this.backgroundDrawable;
21 window.mOnDismissListener = this.onDismissListener;
22 window.mAnimationStyle = this.animationStyle;
23 window.mTouchable = this.touchable;
24 window.mOffsetX = this.offsetX;
25 window.mOffsetY = this.offsetY;
26 window.mGravity = this.gravity;
27 }
28}
显然,这里可以看出 context 和 contentView 或 layoutId 是必须需要设置的,如果没有设置相应的会有错误提示,当然在封装中也对 contentView 和 layoutId 不能同时使用做了限制和如果使用了两者的错误提示。下面是对外提供的显示 PopupWindow 的方法,根据不同的枚举类型将 PopupWindow 显示在不同的位置,具体如下:
1public void showPopupWindow(View v, LocationType type) {
2 if (mView!=null){
3 mPopupWindow.setContentView(mView);
4 }else if (mLayoutId != -1){
5 View contentView = LayoutInflater.from(mContext).inflate(mLayoutId, null);
6 mPopupWindow.setContentView(contentView);
7 }
8 mPopupWindow.setWidth(mWidth);
9 mPopupWindow.setHeight(mHeight);
10 mPopupWindow.setBackgroundDrawable(mBackgroundDrawable);
11 mPopupWindow.setOutsideTouchable(mOutsideTouchable);
12 mPopupWindow.setOnDismissListener(mOnDismissListener);
13 mPopupWindow.setAnimationStyle(mAnimationStyle);
14 mPopupWindow.setTouchable(mTouchable);
15 //获取目标View的坐标
16 int[] locations = new int[2];
17 v.getLocationOnScreen(locations);
18 int left = locations[0];
19 int top = locations[1];
20 //获取PopupWindow的宽高
21 mPopupWindow.getContentView().measure(
22 View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
23 View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
24 int popupWidth = mPopupWindow.getContentView().getMeasuredWidth();
25 int popupHeight = mPopupWindow.getContentView().getMeasuredHeight();
26
27 switch (type) {
28 case TOP_LEFT:
29 mPopupWindow.showAtLocation(v,Gravity.NO_GRAVITY,left - popupWidth + mOffsetX,top - popupHeight + mOffsetY);
30 break;
31 case TOP_CENTER:
32 int offsetX = (v.getWidth() - popupWidth) / 2;
33 mPopupWindow.showAtLocation(v,Gravity.NO_GRAVITY,left + offsetX + mOffsetX,top - popupHeight + mOffsetY);
34 break;
35 case TOP_RIGHT:
36 mPopupWindow.showAtLocation(v,Gravity.NO_GRAVITY,left + v.getWidth() + mOffsetX,top - popupHeight + mOffsetY);
37 break;
38
39 case BOTTOM_LEFT:
40 mPopupWindow.showAsDropDown(v, -popupWidth + mOffsetX,mOffsetY);
41 break;
42 case BOTTOM_CENTER:
43 int offsetX1 = (v.getWidth() - popupWidth) / 2;
44 mPopupWindow.showAsDropDown(v,offsetX1 + mOffsetX,mOffsetY);
45 break;
46 case BOTTOM_RIGHT:
47 mPopupWindow.showAsDropDown(v, v.getWidth() + mOffsetX,mOffsetY);
48 break;
49
50 case LEFT_TOP:
51 mPopupWindow.showAtLocation(v, Gravity.NO_GRAVITY, left - popupWidth + mOffsetX, top - popupHeight + mOffsetY);
52 break;
53 case LEFT_BOTTOM:
54 mPopupWindow.showAtLocation(v, Gravity.NO_GRAVITY, left - popupWidth + mOffsetX, top + v.getHeight() + mOffsetY);
55 break;
56 case LEFT_CENTER:
57 int offsetY = (v.getHeight() - popupHeight) / 2;
58 mPopupWindow.showAtLocation(v, Gravity.NO_GRAVITY,left - popupWidth + mOffsetX,top + offsetY + mOffsetY);
59 break;
60
61 case RIGHT_TOP:
62 mPopupWindow.showAtLocation(v, Gravity.NO_GRAVITY, left + v.getWidth() + mOffsetX,top - popupHeight + mOffsetY);
63 break;
64 case RIGHT_BOTTOM:
65 mPopupWindow.showAtLocation(v, Gravity.NO_GRAVITY, left + v.getWidth() + mOffsetX,top + v.getHeight() + mOffsetY);
66 break;
67 case RIGHT_CENTER:
68 int offsetY1 = (v.getHeight() - popupHeight) / 2;
69 mPopupWindow.showAtLocation(v, Gravity.NO_GRAVITY,left + v.getWidth() + mOffsetX,top + offsetY1 + mOffsetY);
70 break;
71 case FROM_BOTTOM:
72 mPopupWindow.showAtLocation(v,mGravity,mOffsetX,mOffsetY);
73 break;
74 }
75}
使用封装后的PopupWindow
下面是使用封装后的 PopupWindow,只需四行代码就可以显示一个默认的 PopupWindow 了,具体如下:
1private void showPopupWindow(MPopupWindow.LocationType type) {
2 MPopupWindow popupWindow = new MPopupWindow
3 .Builder(this)
4 .setLayoutId(R.layout.popup_window_layout)
5 .build();
6 popupWindow.showPopupWindow(btnTarget, type);
7}
由于默认 PopupWindow 背景是透明的,建议测试时设置背景更容易看到效果。
显示效果:
下面是 PopupWindow 在各个位置的显示,具体如下:
如果对此比较感兴趣,可以在公众号回复 MPopupWindow 获取源码链接。
推荐阅读: