PopupWindow,顾名思义,就是弹窗,在很多场景下都可以见到它,可以使用任意布局的View作为其内容,这个弹出框是悬浮在当前activity之上的。
Android 中的弹窗基本有两种,一种是AlertDialog,另一种是PopupWindow**,AlertDialog的显示位置是固定的,PopWindow 的显示位置是我们可以设置和调整的**,因此,像项目中的一些场景如:某个功能的提示说明、点击按钮在按钮上方或者下方弹出菜单、新功能弹窗引导等。由于这些弹窗的位置不固定,因此都可以用PopupWindow来做。
基本用法
使用PopupWindow很简单,可以总结为三个步骤:
1.创建PopupWindow对象实例;
2.设置背景、注册事件监听器和添加动画;
3.显示PopupWindow。
其中,第二步是可选的(不过基本上都要进行第二步的设置)。
案例:
// 用于PopupWindow的View
View popupView = View.inflate(this, R.layout.popup_window_layout, null);
//创建PopupWindow
PopupWindow popupWindow = new PopupWindow(popupView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
// 设置 当前获取到焦点(里面的控件才能被点击)
popupWindow.setFocusable(true);
// 设置PopupWindow的背景
popupView.setBackgroundResource(R.color.colorAccent);
//popupView.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
// 设置点击边上消失(要先设置背景样式才有效果),设置为true则点击pop外会消失,设置为false则不会消失。
// 如果设置了popupWindow.setFocusable(true),则设置为false也会失效
popupWindow.setOutsideTouchable(true);
//显示在按钮下方
popupWindow.showAsDropDown(mButton);
显示在屏幕中央:
popupWindow.showAtLocation(this.getWindow().getDecorView(), Gravity.CENTER, 0, 0);
显示:
//直接显示在参照View 的左下方
public void showAsDropDown(View anchor)
// 显示在参照View的左下方,可以通过xoff,yOff,来调节x,y方向的偏移
public void showAsDropDown(View anchor, int xoff, int off)
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity)
//显示在指定位置,相对于整个屏幕的window而言,通过gravity调解显示在左、上、右、下、中. x,y调整两个方向的偏移
public void showAtLocation(View parent, int gravity, int x, int y)
使用setAnimationStyle方法添加动画:
这里的动画是指PopupWindow出现和消失时的动画。默认是直接弹出和消失,这样难免让用户有一种突兀的感觉;如果PopupWindow能够“滑入”屏幕和“滑出”屏幕(或者其他方式),用户体验会更好。
为PopupWindow添加动画可以调用setAnimationStyle
方法,该方法只有一个参数,就是指定动画的样式,因此我们需要定义动画资源和样式资源。
下面是一个“滑入滑出”动画:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromYDelta="100%p"
android:toYDelta="0"
android:duration="300"/>
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromYDelta="0"
android:toYDelta="100%p"
android:duration="300"/>
</set>
<!-- res/values/styles.xml -->
<style name="animTranslate">
<item name="android:windowEnterAnimation">@anim/translate_in</item>
<item name="android:windowExitAnimation">@anim/translate_out</item>
</style>
现在PopupWindow的出现/消失已经不是那么突兀了。不过,当弹窗出现后,发现弹窗和背景不是很容易区分,如果此时弹窗的背景能“变暗”就好了。
没问题,我们可以在弹窗出现后让背景变暗,并在弹窗消失后让背景还原:
popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
WindowManager.LayoutParams lp=getWindow().getAttributes();
lp.alpha=1.0f;
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
getWindow().setAttributes(lp);
}
});
WindowManager.LayoutParams lp=getWindow().getAttributes();
lp.alpha=0.3f;
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
getWindow().setAttributes(lp);
完整代码
public class MyPopupWindow extends PopupWindow {
private Context mContext;
private PopupWindow mPopupWindow;
private Window window;
private int width = ViewGroup.LayoutParams.WRAP_CONTENT;
private int height = ViewGroup.LayoutParams.WRAP_CONTENT;
public MyPopupWindow(Context context) {
mContext = context;
init();
}
public MyPopupWindow(Context context, int width, int height) {
mContext = context;
this.width = width;
this.height = height;
Activity activity = (Activity) mContext;
window = activity.getWindow();
init();
}
private void init() {
View popupView = LayoutInflater.from(mContext).inflate(R.layout.popup_window_layout, null, false);
//mPopupWindow = new PopupWindow(popupView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mPopupWindow = new PopupWindow(popupView, width, height);
/* //设置宽度
setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
//设置高度为屏幕高度3/5
WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Point point = new Point();
windowManager.getDefaultDisplay().getSize(point);
int windowH = point.y;
setHeight((windowH * 3) / 5);*/
//设置当前获取到焦点(里面的控件才能被点击)
mPopupWindow.setFocusable(true);
//设置PopupWindow的背景
popupView.setBackgroundResource(R.color.colorAccent);
//popupView.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
// 设置点击边上消失(要先设置背景样式才有效果),设置为true则点击pop外会消失,设置为false则不会消失。
// 如果设置了popupWindow.setFocusable(true),则设置为false也会失效
mPopupWindow.setOutsideTouchable(true);
//设置动画
mPopupWindow.setAnimationStyle(R.style.animTranslate);
//让背景变暗
//setAlpha(0.3f);
}
/**
* 显示在控件的下方
*
* @param view
*/
public void show(View view) {
if (!mPopupWindow.isShowing()) {
mPopupWindow.showAsDropDown(view);
}
}
/**
* 显示在参照View的左下方,可以通过xoff,yOff,来调节x,y方向的偏移
*
* @param view
* @param xoff
* @param yoff
*/
public void show(View view, int xoff, int yoff) {
if (!mPopupWindow.isShowing()) {
mPopupWindow.showAsDropDown(view, xoff, yoff);
}
}
/**
* 显示在屏幕中央
*/
public void showCenter() {
//显示在指定位置,相对于整个屏幕的window而言,通过gravity调解显示在左、上、右、下、中. x,y调整两个方向的偏移
if (!mPopupWindow.isShowing()) {
mPopupWindow.showAtLocation(window.getDecorView(), Gravity.CENTER, 0, 0);
}
}
/**
* 让背景变暗
*
* @param alpha
*/
public void setAlpha(float alpha) {
mPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
WindowManager.LayoutParams lp = window.getAttributes();
lp.alpha = 1.0f;
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
window.setAttributes(lp);
}
});
WindowManager.LayoutParams lp = window.getAttributes();
lp.alpha = alpha;
window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
window.setAttributes(lp);
}
}
PopupWindow常用方法
设置内容View
setContentView(View contentView)
除了正常在实例化PopupWindow的时候直接将view传入也可以用这个方法在实例化后重新配置需要的view
设置PopupWindow宽度与高度
setWidth(int width)
setHeight(int height)
除了正常实例化的时候传入宽度与高度,也可以用这个2个方法在实例化后在重新配置需要的宽度与高度
设置PopupWindow背景
setBackgroundDrawable(Drawable background)
设置外部点击退出
setOutsideTouchable(boolean touchable)
设置PopupWindow可聚焦
setFocusable(boolean focusable)
除了一般的聚焦选中功能,还有一个用处重点!重点!重点!设置了可聚焦后,返回back键按下后,不会直接退出当前activity而是先退出当前的PopupWindow.
设置弹窗弹出的动画高度
setElevation(float elevation)
原本没有设置,弹窗的弹出动画效果位置就只会在控件附件,但是设置后弹窗的起始动画位置就变更远了
PopupWindow的封装:
package com.xiaoyehai.dialogtest;
import android.content.Context;
import android.view.View;
import android.widget.PopupWindow;
/**
* Created by xiaoyehai on 2018/6/15 0015.
*/
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);
}
measureWidthAndHeight(popupWindow.controller.mPopupView);
return popupWindow;
}
private void measureWidthAndHeight(View popupView) {
//设置测量模式为UNSPECIFIED可以确保测量不受父View的影响
int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
popupView.measure(w, h);
//得到测量宽度
int mWidth=popupView.getMeasuredWidth();
//得到测量高度
int mHeight=popupView.getMeasuredHeight();
}
}
}
package com.xiaoyehai.dialogtest;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.PopupWindow;
/**
* Created by xiaoyehai on 2018/6/15 0015.
*/
public 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);
}
}
}
}
使用:
CommonPopupWindow popupWindow = new CommonPopupWindow.Builder(this)
.setView(R.layout.popup_window_layout)
.setWidthAndHeight(400, 400)
//设置动画
.setAnimationStyle(R.style.AnimHorizontal)
//设置背景颜色,取值范围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");
}
})
.setOutsideTouchable(true)
.create();
popupWindow.showAtLocation(this.getWindow().getDecorView(), Gravity.CENTER, 0, 0);