简介
PopupWindow,顾名思义弹窗.PopupWindow是与AlertDialog在形式上类似的弹窗功能,都是为了在activity最上层显示一个弹窗.但是区别是PopupWindow可以自定义出现的位置,并且可以添加入自己需要的View或者导入自己写好的xml布局
应用场景
在很多场景下都可以见到它。例如ActionBar/Toolbar的选项弹窗,一组选项的容器,或者列表等集合的窗口等等。
简单的Demo
为了了解基本的流程,我们来一个最简单demo演示一下.
创建流程:
- 用LayoutInflater获得xml布局View .或者直接在代码上new一个View
- 实例化一个PopupWindow,将View在实例化作为参数传入
- 配置PopupWindow参数
代码:
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
TextView textView = new TextView(Main2Activity.this);
textView.setText("测试文本");
final PopupWindow popupWindow = new PopupWindow(textView,200,300);//参数为1.View 2.宽度 3.高度
popupWindow.setOutsideTouchable(true);//设置点击外部区域可以取消popupWindow
mTestButton = (Button)findViewById(R.id.test_btn);
mTestButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
popupWindow.showAsDropDown(mTestButton);//设置popupWindow显示,并且告诉它显示在那个View下面
}
});
}
效果图:
PopupWindow的配置参数详解
设置内容View
setContentView(View contentView)
除了正常在实例化PopupWindow的时候直接将view传入也可以用这个方法在实例化后重新配置需要的view
设置PopupWindow宽度与高度
setWidth(int width)
setHeight(int height)
除了正常实例化的时候传入宽度与高度,也可以用这个2个方法在实例化后在重新配置需要的宽度与高度
设置PopupWindow背景
setBackgroundDrawable(Drawable background)
final PopupWindow popupWindow = new PopupWindow(textView,200,300);
popupWindow.setOutsideTouchable(true);
popupWindow.setBackgroundDrawable(getResources().getDrawable(R.drawable.ic\_launcher\_background));//设置背景
mTestButton = (Button)findViewById(R.id.test_btn);
mTestButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
popupWindow.showAsDropDown(mTestButton);
}
});
设置外部点击退出
setOutsideTouchable(boolean touchable)
这个我在上面的代码已经演示过了
设置PopupWindow可聚焦
setFocusable(boolean focusable)
除了一般的聚焦选中功能,还有一个用处重点!重点!重点!设置了可聚焦后,返回back键按下后,不会直接退出当前activity而是先退出当前的PopupWindow.
设置弹窗弹出的动画高度
setElevation(float elevation)
原本没有设置,弹窗的弹出动画效果位置就只会在控件附件,但是设置后弹窗的起始动画位置就变更远了.请看下面的效果图:
popupWindow.setElevation(1000f);//我将动画位置设置为1000f
设置显示方法参数
// 传入 AnchorView ,锚点实际为 Window
// Gravity.TOP 在该锚点的正上方
// Gravity.LEFT 在屏幕左侧
// Gravity.NO_GRAVITY,在屏幕左上角
// x 为坐标系 x轴方向的偏移量,左负右正
// y 为坐标系 y轴方向的偏移量,上负下正
popupWindow.showAtLocation(view, Gravity.TOP, 0, y);
popupWindow.showAtLocation(view, Gravity.NO_GRAVITY,x, y);
popupWindow.showAtLocation(view, Gravity.TOP, 0, y);
显示提供了两种形式:
showAtLocation()显示View的内部在指定位置()
有两个方法重载:
这个属性一般使用在在整个Activity的window里显示,也就是在整个屏幕内,这个也支持在指定View的内部找到锚点.
例子1:如果你需要在一个布局的里面的下方显示就只需要设置属性为
popupWindow.showAtLocation(view,Gravity.BOTTOM,0,0);
例子2:如果你需要在Activity的window里显示的下方显示就需要设置属性为:
popupWindow.showAtLocation(activity.getWindow().getDecorView(),Gravity.BOTTOM,0,0);
public void showAtLocation(View parent, int gravity, int x, int y)
public void showAtLocation(IBinder token, int gravity, int x, int y)
showAsDropDown()显示在一个参照物View的外部周围
有三个方法重载:
** 注意!这里参照物View的周围,使用这个方法是无法在View的内部找到锚点,它的锚点都是围绕者View的外部四周**
public void showAsDropDown(View anchor)
public void showAsDropDown(View anchor, int xoff, int yoff)
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity)
带Gravity参数的方法是API 19新引入的。
这里的xoff与yoff是对应view的坐标偏移量,对应的初始坐标位置是view的左下角.
**请注意!**在实际使用showAsDropDown()方法的时候,如果只使用showAsDropDown(View anchor, int xoff, int yoff),这个形参会出现在一些品牌的机型上出现一些问题。原因是有些机型初始坐标其实不一定在左下角,而是在左上角。所以,你这个时候设置yoff值就会出现2种不同去情况。怎么解决这个问题呢? 就是尽量使用showAsDropDown(View anchor, int xoff, int yoff, int gravity),这个形参主动设置第4个int gravity的值,来确定初始坐标的位置。
这里我们用一个demo演示一下,假设我现在需要把显示位置移动到目标控件的右上角就需要如下代码:
popupWindow.showAsDropDown(mTestButton,mTestButton.getWidth(),-mTestButton.getHeight())
设置PopupWindow叠放效果
setOverlapAnchor(true);
当然你把PopupWindow的位置设置到可以完全覆盖你指定位置显示PopupWindow的view时,如果设置这个方法为true,那么你无论如何都无法覆盖这个view始终会让这个view漏出一小部分,如效果图所示:
设置PopupWindow可触摸
setTouchable(true);
设置false后,将会阻止PopupWindow窗口里的所有点击事件
设置PopupWindow监听拦截指定触摸事件
popupWindow.setTouchInterceptor(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
//如果这里设置返回true,说明你会消耗这个触摸事件,不会向下传递到内容view里
return false;
}
});
设置PopupWindow覆盖状态栏或者超过屏幕尺寸
允许弹出窗口扩展到屏幕范围之外。默认情况下,窗口被裁剪到屏幕边界。将其设置为false将允许精确定位窗口
popupWindow.setClippingEnabled(false);
设置PopupWindow监听取消事件
popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
}
});
设置PopWindow遮罩层
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.alpha = 0.5f;
getWindow().setAttributes(lp);
pop.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.alpha = 1f;
getWindow().setAttributes(lp);
}
});
一些问题总结
解决Android7.0调用showAsDropDown方法失效问题
原理是重写showAsDropDown,将PopupWindow对话框的高度设置成整个屏幕这么大,然后在减去这View指定的底部坐标值。有点治标不治本的味道。
但是,在个别对话框宽度是屏幕的宽度,但是高度是某个View下边与屏幕底部的长度的时候有很好的实现。
如图所示:
没有对话框的时候
有对话框的时候
@Override
public void showAsDropDown(View anchor) {
if(Build.VERSION.SDK_INT >= 24) {
Rect rect = new Rect();
anchor.getGlobalVisibleRect(rect);
DisplayMetrics outMetrics = new DisplayMetrics();
Context context = anchor.getContext();
((Activity) context).getWindowManager().getDefaultDisplay().getRealMetrics(outMetrics);
int h = outMetrics.heightPixels - rect.bottom;
setHeight(h);
}
super.showAsDropDown(anchor);
}
ListPopupWindow
怎样使用 ListPopUpWindow
和PopUpWindow 相比,它更适合展示多条数据,内部包含了一个 ListView ,那就意味着需要 Adapter 进行数据的绑定。
listPopupWindow = new ListPopupWindow(getContext());
// 适配器添加数据
listPopupWindowAdapter.addAdapterList(list);
// 添加适配器
listPopupWindow.setAdapter(listPopupWindowAdapter);
// 设置弹窗的大小和位置
listPopupWindow.setWidth(width+horizontalOffset*2);
listPopupWindow.setHeight(height);
listPopupWindow.setAnchorView(view);
listPopupWindow.setModal(true);
listPopupWindow.setVerticalOffset(-VerticalOffset);
listPopupWindow.setHorizontalOffset(-horizontalOffset*4);
// 设置背景
listPopupWindow.setBackgroundDrawable(
BitmapUtils.getDrawableFromResource(getContext(), RUtils.drawable(getContext(),"tutu_area_code_background")));
虚拟按键对 PopUpWindow 的影响
虚拟按键的机型在横屏状态下,会造成一个 x 轴方向的偏移(根据具体代码确定),所以我们使用神器 getLocationInWindow,获取锚点 View 在当前 Window 的坐标,然后通过计算确定弹窗出现位置。
具体决定方案,请看 Fucking Code
// 获取锚点 View 在屏幕中的坐标
int[] location = new int[2];
back.getLocationInWindow(location);
int x = location[0];//获取当前位置的横坐标
int y = location[1];//获取当前位置的纵坐标
// 竖屏不做处理
if (VERTICAL_SCREEN == getContext().getResources().getConfiguration().orientation){
popupWindow.showAtLocation(view, Gravity.TOP, 0, y);
}
// 横屏状态
else if (HORIZONTALL_SCREEN == getContext().getResources().getConfiguration().orientation) {
// 检测是否有虚拟按键
if (checkDeviceHasNavigationBar(getContext())){
popupWindow.showAtLocation(view, Gravity.NO_GRAVITY,x, y);
}else {
popupWindow.showAtLocation(view, Gravity.TOP, 0, y);
}
}