刚接手PopupWindow的时候,我们都可能觉得很简单,因为它确实很简单,不过运气不好的可能就会踩到一个坑:

点击PopupWindow最外层布局以及点击返回键PopupWindow不会消失
新手在遇到这个问题的时候可能会折腾半天,最后通过强大的网络找到一个解决方案,那就是跟PopupWindow设置一个背景
popupWindow.setBackgroundDrawable(drawable),这个drawable随便一个什么类型的都可以,只要不为空。
Demo地址:SmartPopupWindow_jb51.rar
下面从源码(我看的是android-22)上看看到底发生了什么事情导致返回键不能消失弹出框:
先看看弹出框显示的时候代码showAsDropDown,里面有个preparePopup方法。
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
if (isShowing() || mContentView == null) {
return;
}
registerForScrollChanged(anchor, xoff, yoff, gravity);
mIsShowing = true;
mIsDropdown = true;
WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
preparePopup(p);
updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity));
if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
p.windowAnimations = computeAnimationResource();
invokePopup(p);
}
再看preparePopup方法
/**
* <p>Prepare the popup by embedding in into a new ViewGroup if the
* background drawable is not null. If embedding is required, the layout
* parameters' height is modified to take into account the background's
* padding.</p>
*
* @param p the layout parameters of the popup's content view
*/
private void preparePopup(WindowManager.LayoutParams p) {
if (mContentView == null || mContext == null || mWindowManager == null) {
throw new IllegalStateException("You must specify a valid content view by "
+ "calling setContentView() before attempting to show the popup.");
}
if (mBackground != null) {
final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
int height = ViewGroup.LayoutParams.MATCH_PARENT;
if (layoutParams != null &&
layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
height = ViewGroup.LayoutParams.WRAP_CONTENT;
}
// when a background is available, we embed the content view
// within another view that owns the background drawable
PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, height
);
popupViewContainer.setBackground(mBackground);
popupViewContainer.addView(mContentView, listParams);
mPopupView = popupViewContainer;
} else {
mPopupView = mContentView;
}
mPopupView.setElevation(mElevation);
mPopupViewInitialLayoutDirectionInherited =
(mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
mPopupWidth = p.width;
mPopupHeight = p.height;
}
上面可以看到mBackground不为空的时候,会PopupViewContainer作为mContentView的Parent,下面看看PopupViewContainer到底干了什么
private class PopupViewContainer extends FrameLayout {
private static final String TAG = "PopupWindow.PopupViewContainer";
public PopupViewContainer(Context context) {
super(context);
}
@Override
protected int[] onCreateDrawableState(int extraSpace) {
if (mAboveAnchor) {
// 1 more needed for the above anchor state
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);
return drawableState;
} else {
return super.onCreateDrawableState(extraSpace);
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) { // 这个方法里面实现了返回键处理逻辑,会调用dismiss
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
if (getKeyDispatcherState() == null) {
return super.dispatchKeyEvent(event);
}
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getRepeatCount() == 0) {
KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null) {
state.startTracking(event, this);
}
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null && state.isTracking(event) && !event.isCanceled()) {
dismiss();
return true;
}
}
return super.dispatchKeyEvent(event);
} else {
return super.dispatchKeyEvent(event);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
return true;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) { // 这个方法里面实现点击消失逻辑
final int x = (int) event.getX();
final int y = (int) event.getY();
if ((event.getAction() == MotionEvent.ACTION_DOWN)
&& ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
dismiss();
return true;
} else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
dismiss();
return true;
} else {
return super.onTouchEvent(event);
}
}
@Override
public void sendAccessibilityEvent(int eventType) {
// clinets are interested in the content not the container, make it event source
if (mContentView != null) {
mContentView.sendAccessibilityEvent(eventType);
} else {
super.sendAccessibilityEvent(eventType);
}
}
}
看到上面红色部分的标注可以看出,这个内部类里面封装了处理返回键退出和点击外部退出的逻辑,但是这个类对象的构造过程中(preparePopup方法中)却有个mBackground != null的条件才会创建
而mBackground对象在setBackgroundDrawable方法中被赋值,看到这里应该就明白一切了。
/**
* Specifies the background drawable for this popup window. The background
* can be set to {@code null}.
*
* @param background the popup's background
* @see #getBackground()
* @attr ref android.R.styleable#PopupWindow_popupBackground
*/
public void setBackgroundDrawable(Drawable background) {
mBackground = background;
// 省略其他的
}
setBackgroundDrawable方法除了被外部调用,构造方法中也会调用,默认是从系统资源中取的
/**
* <p>Create a new, empty, non focusable popup window of dimension (0,0).</p>
*
* <p>The popup does not provide a background.</p>
*/
public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mContext = context;
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes);
final Drawable bg = a.getDrawable(R.styleable.PopupWindow_popupBackground);
mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0);
mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false);
final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1);
mAnimationStyle = animStyle == R.style.Animation_PopupWindow ? -1 : animStyle;
a.recycle();
setBackgroundDrawable(bg);
}
有些版本没有,android6.0版本preparePopup如下:
/**
* Prepare the popup by embedding it into a new ViewGroup if the background
* drawable is not null. If embedding is required, the layout parameters'
* height is modified to take into account the background's padding.
*
* @param p the layout parameters of the popup's content view
*/
private void preparePopup(WindowManager.LayoutParams p) {
if (mContentView == null || mContext == null || mWindowManager == null) {
throw new IllegalStateException("You must specify a valid content view by "
+ "calling setContentView() before attempting to show the popup.");
}
// The old decor view may be transitioning out. Make sure it finishes
// and cleans up before we try to create another one.
if (mDecorView != null) {
mDecorView.cancelTransitions();
}
// When a background is available, we embed the content view within
// another view that owns the background drawable.
if (mBackground != null) {
mBackgroundView = createBackgroundView(mContentView);
mBackgroundView.setBackground(mBackground);
} else {
mBackgroundView = mContentView;
}
mDecorView = createDecorView(mBackgroundView);
// The background owner should be elevated so that it casts a shadow.
mBackgroundView.setElevation(mElevation);
// We may wrap that in another view, so we'll need to manually specify
// the surface insets.
final int surfaceInset = (int) Math.ceil(mBackgroundView.getZ() * 2);
p.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
p.hasManualSurfaceInsets = true;
mPopupViewInitialLayoutDirectionInherited =
(mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
mPopupWidth = p.width;
mPopupHeight = p.height;
}
这里实现返回键监听的代码是mDecorView = createDecorView(mBackgroundView),这个并没有受到那个mBackground变量的控制,所以这个版本应该没有我们所描述的问题,感兴趣的可以自己去尝试一下
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# popupwindow点击消失
# popupwindow不消失
# popupwindow
# 消失
# Android开发实现Fragment监听返回键事件功能的方法
# Android 拦截返回键事件的实例详解
# Android 实现按两次返回键退出程序(两种方法)
# Android实现点击两次返回键退出
# Android使alertDialog.builder不会点击外面和按返回键消失的方法
# Android开发返回键明暗点击效果的实例代码
# 有个
# 很简单
# 弹出
# 为空
# 的是
# 就会
# 也会
# 我看
# 才会
# 其他的
# 这个问题
# 是从
# 感兴趣
# 半天
# 可以看到
# 因为它
# 可以看出
# 再看
# 装了
# 发生了什么
相关文章:
广州网站设计制作一条龙,广州巨网网络科技有限公司是干什么的?
开封网站制作公司,网络用语开封是什么意思?
制作网站的模板软件,网站怎么建设?
七夕网站制作视频,七夕大促活动怎么报名?
c# 在高并发下使用反射发射(Reflection.Emit)的性能
h5网站制作工具有哪些,h5页面制作工具有哪些?
如何用y主机助手快速搭建网站?
网站建设制作需要多少钱费用,自己做一个网站要多少钱,模板一般多少钱?
如何在建站主机中优化服务器配置?
广州商城建站系统开发成本与周期如何控制?
零基础网站服务器架设实战:轻量应用与域名解析配置指南
网站插件制作软件免费下载,网页视频怎么下到本地插件?
视频网站制作教程,怎么样制作优酷网的小视频?
郑州企业网站制作公司,郑州招聘网站有哪些?
购物网站制作费用多少,开办网上购物网站,需要办理哪些手续?
如何用景安虚拟主机手机版绑定域名建站?
如何在阿里云通过域名搭建网站?
阿里云网站搭建费用解析:服务器价格与建站成本优化指南
如何破解联通资金短缺导致的基站建设难题?
建站主机系统SEO优化与智能配置核心关键词操作指南
存储型VPS适合搭建中小型网站吗?
深圳企业网站制作设计,在深圳如何网上全流程注册公司?
我的世界制作壁纸网站下载,手机怎么换我的世界壁纸?
香港服务器选型指南:免备案配置与高效建站方案解析
最好的网站制作公司,网购哪个网站口碑最好,推荐几个?谢谢?
测试制作网站有哪些,测试性取向的权威测试或者网站?
建站之星安装步骤有哪些常见问题?
如何基于云服务器快速搭建个人网站?
建站主机解析:虚拟主机配置与服务器选择指南
建站之星在线版空间:自助建站+智能模板一键生成方案
网站网页制作专业公司,怎样制作自己的网页?
建站之星后台密码如何安全设置与找回?
微网站制作教程,不会写代码,不会编程,怎么样建自己的网站?
*服务器网站为何频现安全漏洞?
独立制作一个网站多少钱,建立网站需要花多少钱?
义乌企业网站制作公司,请问义乌比较好的批发小商品的网站是什么?
厦门模型网站设计制作公司,厦门航空飞机模型掉色怎么办?
如何自己制作一个网站链接,如何制作一个企业网站,建设网站的基本步骤有哪些?
如何制作一个表白网站视频,关于勇敢表白的小标题?
网站制作哪家好,cc、.co、.cm哪个域名更适合做网站?
专业型网站制作公司有哪些,我设计专业的,谁给推荐几个设计师兼职类的网站?
香港服务器如何优化才能显著提升网站加载速度?
如何在腾讯云免费申请建站?
如何获取上海专业网站定制建站电话?
如何在阿里云ECS服务器部署织梦CMS网站?
在线制作视频的网站有哪些,电脑如何制作视频短片?
如何通过wdcp面板快速创建网站?
建站DNS解析失败?如何正确配置域名服务器?
建站主机服务器选购指南:轻量应用与VPS配置解析
PHP正则匹配日期和时间(时间戳转换)的实例代码
*请认真填写需求信息,我们会在24小时内与您取得联系。