PopupWindow使用教程

Catalogue
  1. 1 创建
  2. 2 显示
    1. 2.1 附着某个控件showAsDropDown。
    2. 2.2 设置屏幕坐标showAtLocation。
    3. 2.3 隐藏PopupWindow
  3. 3 状态
    1. 3.1 外部被点击取消
    2. 3.2 解决NavigationBar重叠
    3. 3.3 可获取焦点
    4. 3.4 遮盖附着View
    5. 3.5 窗口裁剪
  4. 4 动画效果
    1. 4.1 设置动画
    2. 4.2 进入和退出动画
  5. 5 输入模式
    1. 5.1 输入模式
    2. 5.2 软键盘模式
  6. 6 监听事件
    1. 6.1 隐藏事件监听
    2. 6.2 触摸事件拦截
  7. 7 更新
    1. 7.1 更新状态
    2. 7.2 更新尺寸
    3. 7.3 更新显示位置
    4. 7.4 相对位置更新
  8. 8 背景变灰
  9. 9 开发过程中遇到的坑
  10. 参考资料

PopupWindow是android.widget中一个弹框控件,与普通dialog相比,它的优势在于可以任意改变其在界面中的位置。本篇我们就来好好研究一下它的用法吧。

1 创建

PopupWindow有两种创建方式:
1、通过视图创建,这是最常用的方法。

1
2
3
4
5
6
7
8
9
10
PopupWindow ()	// 创建一个空的PopupWindow

PopupWindow (View contentView)

PopupWindow (int width, int height)

PopupWindow (View contentView, // PopupWindow的内容View, 相当于setContentView
int width, // 宽, 相当于setwidth()
int height,// 高, 相当于setHeight
boolean focusable)//是否可获取焦点, 相当于setFocusable()

2、通过上下文创建,一般不使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
PopupWindow (Context context)

PopupWindow (Context context,
AttributeSet attrs)

PopupWindow (Context context,
AttributeSet attrs,
int defStyleAttr)

PopupWindow (Context context,
AttributeSet attrs,
int defStyleAttr,
int defStyleRes)

创建PopuWindow必要的三个条件:
void setHeight (int height) // 因为PopupWindow没有默认布局所以必须指定宽高
void setWidth (int width)
void setContentView (View contentView) // 需要显示的内容

注意
PopupWindow指定宽高时用LayoutParams.WRAP_CONTENT包裹布局, 这时并不总是布局多大就显示多大,估计是BUG吧。所以遇到这种情况,具体的PopupWindow大小我们还是需要手动计算。

2 显示

显示PopupWindow可以分为两种方式:

2.1 附着某个控件showAsDropDown。

默认是PopupWindow的左上角对其控件的左下角,或者设置Gravity.RIGHT, PopupWindow的右上角对齐控件的右下角。不存在Gravity.TOP或Gravity.BOTTOM效果。

1
2
3
4
5
void showAsDropDown (View anchor) //弹窗显示在anchor控件左下方

void showAsDropDown (View anchor, int xoff, int yoff)//以控件左下角为原点的偏移坐标

void showAsDropDown (View anchor,int xoff,int yoff,int gravity) //弹窗显示在控件的左下方还是右下方, 参数Gravity.RIGHT/Gravity.LEFT. 默认是左下方

2.2 设置屏幕坐标showAtLocation。

当前窗口的任意位置(setClippingEnabled设置为ture时,不包括状态栏。)

1
2
3
4
5
void showAtLocation (
View parent, //只要是屏幕上任意控件对象即可
int gravity, //屏幕位置
int x, //偏移坐标
int y)

parent:该属性只要是当前任意控件对象即可(View和ViewGroup都行), 官方文档介绍该对象参数主要是为了得到该对象的getWindowToken()方法。
需要注意的是多次调用show方法只会执行第一句

1
2
3
4
mPopupWindow.showAtLocation(popupwindow, Gravity.TOP, 100, 0); // 只有该行生效
mPopupWindow.showAtLocation(popupwindow, Gravity.LEFT, 100, 0);
mPopupWindow.showAtLocation(popupwindow, Gravity.RIGHT, 100, 0);
mPopupWindow.showAtLocation(popupwindow, Gravity.BOTTOM, 100, 0);

2.3 隐藏PopupWindow

1
void dismiss ()

3 状态

3.1 外部被点击取消

如果为true点击PopupWindow外部区域可以取消PopupWindow

1
void setOutsideTouchable (boolean touchable) // 设置外部是否可被点击

但是在android6.0以下还是无法点击外部取消Popupwindow. 可以通过设置背景来解决这个Bug。

1
mPopupWindow.setBackgroundDrawable(new BitmapDrawable());

3.2 解决NavigationBar重叠

这是Android5.0(API22)后添加的方法, 默认为true,为true时将不会与导航栏重叠。

1
void setAttachedInDecor (boolean enabled)

3.3 可获取焦点

一般控件都不需要焦点,但是输入框EditText需要先获取焦点才能输入。 最重要的是当PopupWindow可获取焦点时按下手机返回键将不会销毁当前Activity而是关闭当前PopupWindow。所以我们一般还是设置为true, 更加符合用户操作逻辑。该方法为true时同时拥有setOutsideTouchable(true)的作用。

1
void setFocusable (boolean focusable)

3.4 遮盖附着View

1
void setOverlapAnchor (boolean overlapAnchor)

PopupWindow对齐方式从View anchor的左下角变成了左上角了。

3.5 窗口裁剪

PopupWindow默认是不会超出屏幕边界的. 但是如果该方法为false时会采用精准位置, 能超出屏幕范围.

1
void setClippingEnabled (boolean enabled)

4 动画效果

4.1 设置动画

可以设置popupWindow的显示和隐藏动画。

1
void setAnimationStyle (int animationStyle)

可以看到方法是传入一个Style的样式id,示例:

1
2
3
4
<style name="popupwindow_anim_style">
<item name="android:windowEnterAnimation">@anim/dialog_bottom_enter</item>
<item name="android:windowExitAnimation">@anim/dialog_bottom_exit</item>
</style>

分别由两个属性组成. 两个属性各代表一个anim动画文件.

4.2 进入和退出动画

这是在Android6.0(API 23)后加入的方法. 配合Material Design的转场动画使用.
进入动画

1
void setEnterTransition (Transition enterTransition)

退出动画

1
void setExitTransition (Transition exitTransition)

5 输入模式

针对PopupWindow中包含EditText控件.

5.1 输入模式

1
void setInputMethodMode (int mode)

支持三种模式:
1、INPUT_METHOD_FROM_FOCUSABLE 根据可否获取焦点判断是否可输入
2、INPUT_METHOD_NEEDED 允许输入
3、INPUT_METHOD_NOT_NEEDED 不允许输入

5.2 软键盘模式

1
void setSoftInputMode (int mode) // mode为WindowManager.LayoutParams的softInputMode常量

softInputMode包含九种取值, 可组合使用,分为两类:
显示状态模式

  1. SOFT_INPUT_STATE_UNSPECIFIED 默认模式
  2. SOFT_INPUT_STATE_HIDDEN
  3. SOFT_INPUT_STATE_ALWAYS_HIDDEN 总是隐藏
  4. SOFT_INPUT_STATE_UNCHANGED
  5. SOFT_INPUT_STATE_VISIBLE
  6. SOFT_INPUT_STATE_ALWAYS_VISIBLE 自动弹出软键盘

调整模式

  1. SOFT_INPUT_ADJUST_UNSPECIFIED 默认模式
  2. SOFT_INPUT_ADJUST_RESIZE 软键盘弹出后PopupWindow会自动调整坐标,不被遮挡
  3. SOFT_INPUT_ADJUST_PAN

6 监听事件

6.1 隐藏事件监听

即PopupWindow执行dismiss()后回调的方法。

1
void setOnDismissListener (PopupWindow.OnDismissListener onDismissListener)

6.2 触摸事件拦截

1
void setTouchInterceptor (View.OnTouchListener l)

7 更新

以下的更新PopupWindow都必须在PopupWindow处于以及被显示的状态下才行,且PopupWindow的宽高设置都必须大于等于0, 如果想忽略PopupWindow的宽高设置就设为-1。

7.1 更新状态

该方法不能更新PopupWindow的宽高, 只能更新PopupWindow的状态. 例如更新Focusable和OutsideTouchable。

1
void update () 

7.2 更新尺寸

上面说过update()不能更新PopupWindow的宽高, 但是提供更新宽高的update方法。

1
2
void update (int width, // 更新PopupWindow的宽高
int height)

7.3 更新显示位置

该方法是相当于重新showAsDropDown, 所以这是相对于控件的位置更新

1
2
3
4
5
6
7
8
9
void update (View anchor, // 更新显示控件的位置
int width,
int height)

void update (View anchor,
int xoff, // 相对于控件的偏移值
int yoff,
int width,
int height)

7.4 相对位置更新

相对于当前的位置进行偏移, 不同的显示位置对于的相对原点也不同。
showAsDropDown的相对原点是整个屏幕左上角, 包括状态栏。所以由于包括状态栏所以坐标偏移的时候一定要y轴偏移大于60超出状态栏的高度。 否则因为遮挡状态栏导致PopupWindow无法显示.

1
mPopupWindow.update(50, 60, -1,-1); // x轴偏移50

showAtLocation的相对原点是自身位置

1
2
3
4
5
6
7
8
9
10
void update (int x, // 坐标偏移
int y,
int width, // PopupWindow宽高
int height)

void update (int x,
int y,
int width,
int height,
boolean force) // 可获取焦点

8 背景变灰

当我们弹出PopupWindow时,有时希望背景能够变成灰色,那我们可以通过以下方式实现。

1
2
3
4
5
6
7
8
9
10
11
/**
* 设置添加屏幕的背景透明度
*
* @param bgAlpha 屏幕透明度0.0-1.0 1表示完全不透明
*
*/
public void setBackgroundAlpha(float bgAlpha) {
WindowManager.LayoutParams lp = ((Activity) mContext).getWindow().getAttributes();
lp.alpha = bgAlpha;
((Activity) mContext).getWindow().setAttributes(lp);
}

9 开发过程中遇到的坑

笔者之前使用PopupWindow做了一个全局的转菊花功能,期间将 PopupWindow 成员变量定义成静态变量,每次 show 之前判断该变量是否为 null ,为 null 时赋值,最后导致了有时菊花无法弹出的问题。所以,如果需要做菊花功能,建议每个界面都创建一次 PopuWindow 实例。当然使用 PopuWindow 做菊花并不常见,一般的做法是使用 DialogFragment 完成。


参考资料

PopupWindow最全使用说明