我们在开发中经常需要弹出提示信息或者弹出一个子框框用来让用户选择一些细节化的东西,我们可以选择用 Dialog 对话框来实现(官方貌似更推荐用 DialogFragment 来替代 Dialog,这个今后再说),也可以选择一个弹出框 PopupWindow 来实现,我在开发中也使用了很多次 PopupWindow 了,也记录一下它的基本使用。
基本效果就像这样的:
1 基本使用
新建一个工程,在 Activity 中放一个 Button ,点击该 Button 显示 PopupWindow,先创建一个 PopupWindow 的布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#000000"
android:gravity="center_horizontal"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_in_popupwindow"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="PopupWindow Button1"
android:textColor="#FFFFFF"
android:textSize="18sp" />
<Button
android:id="@+id/btn_in_popupwindow"
android:text="按钮"
android:textColor="#FFFFFF"
android:background="@null"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/iv_in_popupwindow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher" />
</LinearLayout>
然后在 Button 的点击事件中创建 PopupWindow:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void showPopupWindow(View view) {
//加载一个布局作为 PopupWindow 的布局
View contentView = LayoutInflater.from(this).inflate(R.layout.popupwindow_demo, null);
//创建一个 PopupWindow
PopupWindow mPopupWindow = new PopupWindow();
//设置 PopupWindow 的布局
mPopupWindow.setContentView(contentView);
//设置 PopupWindow 的宽度
mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
//设置 PopupWindow 的高度
mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
//设置 PopupWindow 不获取焦点
mPopupWindow.setFocusable(false);
//设置 PopupWindow 点击外部可消失
mPopupWindow.setOutsideTouchable(true);
//设置 PopupWindow 的背景,如果不设置 PopupWindow 的背景,无论是点击外部区域还是Back键都无法dismiss弹框
mPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
//设置 PopupWindow 显示和消失时的动画,需在 values 文件夹中的 styles.xml 文件中定义
// <style name="popupwindow_anim">
// <item name="android:windowEnterAnimation">@anim/scale_enter</item>
// <item name="android:windowExitAnimation">@anim/scale_exit</item>
// </style>
mPopupWindow.setAnimationStyle(R.style.popupwindow_anim);
//设置 PopupWindow 消失的监听器
mPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
Toast.makeText(MainActivity.this, "PopupWindow 消失了", Toast.LENGTH_SHORT).show();
}
});
View parentView = LayoutInflater.from(this).inflate(R.layout.activity_main, null);
//设置 PopupWindow 的显示位置
mPopupWindow.showAtLocation(parentView, Gravity.CENTER, 0, 0);
}
}
//监听 PopupWindow 中的控件
contentView.findViewById(R.id.btn_in_popupwindow).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "点击了 PopupWindow 中的 Button", Toast.LENGTH_SHORT).show();
mPopupWindow.dismiss();
}
});
2 PopupWindow 方法说明:
2.1 构造方法:
public PopupWindow();
public PopupWindow(View contentView);
public PopupWindow(int width, int height);
public PopupWindow(View contentView, int width, int height);
public PopupWindow(View contentView, int width, int height, boolean focusable);
public PopupWindow(View contentView);
public PopupWindow(int width, int height);
public PopupWindow(View contentView, int width, int height);
public PopupWindow(View contentView, int width, int height, boolean focusable);
PopupWindow 一共有五个构造方法,我们可以在构造对象的时候就设置好宽高和是否获得焦点,也可以像上面的代码一样单独设置,无论选择哪种构造方法或在什么时候设置,contentView 、 width 和 height 这三个属性是必须的,少任意一个都不能弹出 PopupWindow 。
2.2 显示方法:
public void showAtLocation(View parent, int gravity, int x, int y):显示在指定 Parent
public void showAsDropDown(View anchor):显示在指定 parent 控件的下方。
public void showAsDropDown(View anchor, int xoff, int yoff):显示在指定 parent 控件的下方,xoff 为 x 轴的偏移量,yoff 为 y 轴的偏移量
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity):显示在指定 parent 控件下方的指定位置,xoff 为 x 轴的偏移量,yoff 为 y 轴的偏移量,gravity 为对齐方式,一般主要也就用 Gravity.LEFT(PopupWindow 的左边界与 parent 控件的左边界对齐)和 Gravity.RIGHT (PopupWindow 的左边界与 parent 控件的右边界对齐)。
public void showAsDropDown(View anchor):显示在指定 parent 控件的下方。
public void showAsDropDown(View anchor, int xoff, int yoff):显示在指定 parent 控件的下方,xoff 为 x 轴的偏移量,yoff 为 y 轴的偏移量
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity):显示在指定 parent 控件下方的指定位置,xoff 为 x 轴的偏移量,yoff 为 y 轴的偏移量,gravity 为对齐方式,一般主要也就用 Gravity.LEFT(PopupWindow 的左边界与 parent 控件的左边界对齐)和 Gravity.RIGHT (PopupWindow 的左边界与 parent 控件的右边界对齐)。
比如刚才的代码,我们更改显示方式为(为了让效果明显,PopupWindow 中只保留一个 Button):
mPopupWindow.showAsDropDown(view, 0, 0, Gravity.LEFT);
PopupWindow 的位置就变成这样:
如果是 Gravity.RIGHT 则效果是这样:
2.3 其他常用方法
PopupWindow 还有一些其他常用方法也在上面的代码中有所展示:
setFocusable():设置是否获得焦点,true 为获得焦点,false 则不获得焦点。
setOutsideTouchable():设置点击外部是否可消失,true 为可消失,false 则不会消失。需要注意的时候如果设置为 true 的话需要同时设置 PopupWindow 的背景,否则点击外部 PopupWindow 还是不会消失,原理倒不是很清楚。
setBackgroundDrawable():设置 PopupWindow 的背景,一般为透明背景。
setAnimationStyle():设置 PopupWindow 出现和消失的动画,设置方法在上面代码中有提到,在 API 23以上可以分别设置。
setOnDismissListener():设置 PopupWindow 消失的监听器.
dismiss():使 PopupWindow 消失。
isShowing():判断 PopupWindow 是否已弹出,是则返回 true ,未弹出则返回 false。
setOutsideTouchable():设置点击外部是否可消失,true 为可消失,false 则不会消失。需要注意的时候如果设置为 true 的话需要同时设置 PopupWindow 的背景,否则点击外部 PopupWindow 还是不会消失,原理倒不是很清楚。
setBackgroundDrawable():设置 PopupWindow 的背景,一般为透明背景。
setAnimationStyle():设置 PopupWindow 出现和消失的动画,设置方法在上面代码中有提到,在 API 23以上可以分别设置。
setOnDismissListener():设置 PopupWindow 消失的监听器.
dismiss():使 PopupWindow 消失。
isShowing():判断 PopupWindow 是否已弹出,是则返回 true ,未弹出则返回 false。
3 封装
在第 1 部分基本使用中我们看到创建一个 PopupWindow 还是要写很多行代码的,看起来一点都不优雅,所以我们可以封装一个 PopupWindow 的工具类让其可以链式调用:
public class PopupWindowHelper {
public static class Builder {
private PopupWindow popupWindow;
private View contentView = null;
private int width = 0;
private int height = 0;
//非必须设置的属性,我们设置默认值
private boolean focusable = false;
private boolean outsideTouchable = true;
private Drawable background = new ColorDrawable(Color.TRANSPARENT);
private int animationStyle = 0;
private PopupWindow.OnDismissListener onDismissListener = null;
public Builder() {
this.popupWindow = new PopupWindow();
}
public Builder setContentView(View contentView) {
this.contentView = contentView;
return this;
}
public Builder setWidth(int width) {
this.width = width;
return this;
}
public Builder setHeight(int height) {
this.height = height;
return this;
}
public Builder setFocusable(boolean focusable) {
this.focusable = focusable;
return this;
}
public Builder setOutsideTouchable(boolean outsideTouchable) {
this.outsideTouchable = outsideTouchable;
return this;
}
public Builder setBackgroundDrawable(Drawable background) {
this.background = background;
return this;
}
public Builder setAnimationStyle(int animationStyle) {
this.animationStyle = animationStyle;
return this;
}
public Builder setOnDismissListener(PopupWindow.OnDismissListener onDismissListener) {
this.onDismissListener = onDismissListener;
return this;
}
public PopupWindow create() {
//因为contentView 、 width 和 height 这三个属性是必须的,所以我们在判断如果没有设置这三个参数的话主动抛出异常
//其实 Android 源码中不设置这三个参数并不会抛出异常
if (contentView == null) {
throw new NullPointerException("contentView is null");
} else {
popupWindow.setContentView(contentView);
}
if (width == 0) {
throw new IllegalArgumentException("width = 0");
} else {
popupWindow.setWidth(width);
}
if (height == 0) {
throw new IllegalArgumentException("height = 0");
} else {
popupWindow.setHeight(height);
}
popupWindow.setFocusable(focusable);
popupWindow.setOutsideTouchable(outsideTouchable);
popupWindow.setBackgroundDrawable(background);
popupWindow.setAnimationStyle(animationStyle);
if (onDismissListener != null) {
popupWindow.setOnDismissListener(onDismissListener);
}
return popupWindow;
}
}
}
public void showPopupWindow(View view) {
new PopupWindowHelper.Builder().setContentView(LayoutInflater.from(this).inflate(R.layout.popupwindow_demo, null))
.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT)
.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT)
.setFocusable(false)
.setOutsideTouchable(true)
.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT))
.setAnimationStyle(R.style.popupwindow_anim)
.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
Toast.makeText(MainActivity.this, "PopupWindow 消失了", Toast.LENGTH_SHORT).show();
}
})
.create()
.showAtLocation(LayoutInflater.from(this).inflate(R.layout.activity_main, null), Gravity.CENTER, 0, 0);
}
效果还是同刚开始的一样,但是调用方式看起来就更优雅了一些:
4 小结
其实 PopupWindow 效果跟 Dialog 差不多,使用也比较简单,运用了多次后结合之前看到建造者模式,自己封装了一个工具类,体现了 Java 的基本思想的同时也让调用更优雅。再小的一件事也是可以不断优化的,普通程序员的职责是让普通人的生活变得更简单,优秀程序员的职责是让普通程序员的工作变得更简单,现在移动开发算是在走向低谷了,但是希望自己能够学到更多的思想,在程序猿的道路上越走越好。