作者: 夏至,欢迎转载,但请保留这段申明,谢谢
https://juejin.im/post/5961e03e51882568b13c3308
为了避免重复造轮子,我们一般都会封装一个通用的控件,比如这次,项目中需要用到比较多的 popupwindow ,如果需要一个个写,那么依旧会累死人,而且还是无用功,无意义,所以,封装一个通用的,除了让同事看了直刷666之外,自己还省了很多事情。
先上效果图:
1、如何使用
那么,一般我们配置一个 PopupWindow 正常步骤需要多少代码呢?如下:
PopupWindow popupWindow = new PopupWindow(this);
View contentview = LayoutInflater.from(this).inflate(R.layout.popup_calendar,null);
popupWindow =
new PopupWindow(contentview,
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
true);
//设置取消
popupWindow.setOutsideTouchable(true);
popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
//设置位置
View rootview = LayoutInflater.from(this).inflate(R.layout.activity_main,null);
popupWindow.showAtLocation(rootview,Gravity.CENTER,0,0);
一般我们需要实现上面的基本代码,PopupWindow 才能跑起来,然后我们还需要添加动画,监听back键等等,然后,另外一个需要用到的时候,又得重复写,真的让人很绝望,这个时候,封装的思想就从脑袋冒出来了,那么,封装之后,怎么样的呢?如下:
CustomPopupWindow popupWindow = new CustomPopupWindow.Builder()
.setContext(this) //设置 context
.setContentView(R.layout.popup_calendar) //设置布局文件
.setwidth(LinearLayout.LayoutParams.WRAP_CONTENT) //设置宽度,由于我已经在布局写好,这里就用 wrap_content就好了
.setheight(LinearLayout.LayoutParams.WRAP_CONTENT) //设置高度
.setFouse(true) //设置popupwindow 是否可以获取焦点
.setOutSideCancel(true) //设置点击外部取消
.setAnimationStyle(R.style.popup_anim_style) //设置popupwindow动画
.builder() //
.showAtLocation(R.layout.activity_calendar, Gravity.CENTER,0,0); //设置popupwindow居中显示
注意上面的 showAtLocation 是在 builder 之后的,表示显示正中间;如果想让它显示在某个 view 的相应位置,也可以使用 showAsLocation() 来实现。
至于为什么在 builder() 的后面呢?因为不太确定在用的时候,是显示在父布局的位置,还是显示在某个控件的相应位置,所以,我把代码封装成下面这样:
/**
* 根据父布局,显示位置
* @param rootviewid
* @param gravity
* @param x
* @param y
* @return
*/
public CustomPopupWindow showAtLocation(int rootviewid,int gravity,int x,int y){
if (mPopupWindow != null){
View rootview = LayoutInflater.from(mContext).inflate(rootviewid,null);
mPopupWindow.showAtLocation(rootview,gravity,x,y);
}
return this;
}
当然,你要把它抽出来也可以的;
还有一种常见的情况,我们常用 popupwindow 作用 dialog,那么里面有 button 处理相应的逻辑。那如何想获取 PopupWindow 里面的控件怎么办?为了方便调用,这里我也采用用 id 的形式,所以,调用只要这样即可:
mMonthPicker = (PickerView) popupWindow.getItemView(R.id.picker_month);
然后就可以用 mMonthPicker 这个 view 搞事情了。
这样就把 contentview 中的控件取出来使用了,只要知道 id 就可以了,是不是方便了很多,都挺简单的,大家自己封装一边就ok全明白了。
封装思路
相比封装 listview 和 recyclerview ,这个算是比较简单的,就是观察最原始的代码,提取最核心不变的;无非就是 PopupWindow 的最要布局
- cnotentview ,为了避免每次都来个 layoutinflate ,我们封装成一个 id
- 大小,我们都知道 PopupWindow 没有自己的布局,上面在给了 contentview 之后,大小也要给
- 显示位置,显示就两个函数 ,showAtLocation 和 showAsLocation ,为了方便,我们也写成 id 的方式,当然也可以传入 view
基本就可以了,至于其他附加项,比如动画,点击外部取消,监听back键,或者简单 contentview 控件的事件,都是变动的,所以,用 Builder 的模式构建比较舒服一些。具体就这些了。如果你对 Builder 这中模式不熟悉,可以看我以前文章:
模仿常用框架Builder初始化数据,如何优雅地装逼
3、CustomPopupWindow 完成代码
以下是我现在用的代码,大家可以参考一下,根据自己的需求添加或者删除。
** 动画部分:**
menushow :
menudiss :
布局就不贴出来,由于用到自定义控件,贴出来反而不好,大家根据自己的需求,编写即可。
CustomPopupWindow 完整代码:
public class CustomPopupWindow {
private PopupWindow mPopupWindow;
private View contentview;
private Context mContext;
public CustomPopupWindow(Builder builder) {
mContext = builder.context;
contentview = LayoutInflater.from(mContext).inflate(builder.contentviewid,null);
mPopupWindow =
new PopupWindow(contentview,builder.width,builder.height,builder.fouse);
//需要跟 setBackGroundDrawable 结合
mPopupWindow.setOutsideTouchable(builder.outsidecancel);
mPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
mPopupWindow.setAnimationStyle(builder.animstyle);
}
/**
* popup 消失
*/
public void dismiss(){
if (mPopupWindow != null){
mPopupWindow.dismiss();
}
}
/**
* 根据id获取view
* @param viewid
* @return
*/
public View getItemView(int viewid){
if (mPopupWindow != null){
return this.contentview.findViewById(viewid);
}
return null;
}
/**
* 根据父布局,显示位置
* @param rootviewid
* @param gravity
* @param x
* @param y
* @return
*/
public CustomPopupWindow showAtLocation(int rootviewid,int gravity,int x,int y){
if (mPopupWindow != null){
View rootview = LayoutInflater.from(mContext).inflate(rootviewid,null);
mPopupWindow.showAtLocation(rootview,gravity,x,y);
}
return this;
}
/**
* 根据id获取view ,并显示在该view的位置
* @param targetviewId
* @param gravity
* @param offx
* @param offy
* @return
*/
public CustomPopupWindow showAsLaction(int targetviewId,int gravity,int offx,int offy){
if (mPopupWindow != null){
View targetview = LayoutInflater.from(mContext).inflate(targetviewId,null);
mPopupWindow.showAsDropDown(targetview,gravity,offx,offy);
}
return this;
}
/**
* 显示在 targetview 的不同位置
* @param targetview
* @param gravity
* @param offx
* @param offy
* @return
*/
public CustomPopupWindow showAsLaction(View targetview,int gravity,int offx,int offy){
if (mPopupWindow != null){
mPopupWindow.showAsDropDown(targetview,gravity,offx,offy);
}
return this;
}
/**
* 根据id设置焦点监听
* @param viewid
* @param listener
*/
public void setOnFocusListener(int viewid,View.OnFocusChangeListener listener){
View view = getItemView(viewid);
view.setOnFocusChangeListener(listener);
}
/**
* builder 类
*/
public static class Builder{
private int contentviewid;
private int width;
private int height;
private boolean fouse;
private boolean outsidecancel;
private int animstyle;
private Context context;
public Builder setContext(Context context){
this.context = context;
return this;
}
public Builder setContentView(int contentviewid){
this.contentviewid = contentviewid;
return this;
}
public Builder setwidth(int width){
this.width = width;
return this;
}
public Builder setheight(int height){
this.height = height;
return this;
}
public Builder setFouse(boolean fouse){
this.fouse = fouse;
return this;
}
public Builder setOutSideCancel(boolean outsidecancel){
this.outsidecancel = outsidecancel;
return this;
}
public Builder setAnimationStyle(int animstyle){
this.animstyle = animstyle;
return this;
}
public CustomPopupWindow builder(){
return new CustomPopupWindow(this);
}
}
}