popwindow 在安卓7.0上显示位置错误以及在6.0下点击外部不能消失的情况解析
来源:互联网 发布:淘宝官微 编辑:程序博客网 时间:2024/06/05 03:42
popwindow在日常使用的过程中频率很高 ,一般用起来也是得心应手,但是也是有很多坑存在的,在这个版本的迭代中就遇到了一些问题,解决起来很简单,但是以后开发中肯定要注意的
popwindow 出现的View not attached to window manager
在这个版本的迭代中,产品要求了很多xx秒自动消失的需求,需求很简单,洋洋洒洒写了如下代码:
PopupWindow popupWindow = new PopupWindow(context); View view = WorthbuyImageUtil.inflate(context,layoutId,null); popupWindow.setContentView(view); popupWindow.setBackgroundDrawable(null); popupWindow.setWidth(DPIUtil.dip2px(302.5f)); popupWindow.setHeight(DPIUtil.dip2px(53)); popupWindow.setOutsideTouchable(false);// if (!((BaseActivity) context).isFinishing()) { popupWindow.showAtLocation(anchorView, Gravity.BOTTOM, 0, DPIUtil.dip2px(60));// } final Handler handler = new Handler(Looper.getMainLooper()); handler.postDelayed(new Runnable() { @Override![这里写图片描述](http://img.blog.csdn.net/20170810194212157?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd3pseWQx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) public void run() {// if (!((BaseActivity) context).isFinishing() && popupWindow.isShowing()) { popupWindow.dismiss(); handler.removeCallbacks(this);// } } }, 3000); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (listener!=null){ listener.onClick(v); } } }); }
测试的时候都是ok的,毕竟等待事件不是很长,3s或者1s,但是上线后就出现了bug,虽然复现率很低,但是也要解决的,问题就在于,popwindow的显示与消失是依赖于当前window的。引发这个的原因基本上都一致都是Dismiss对话框的时候,Activity已经不再存在。
所以问题的解决方式就很简单了,就是添加上上面注释掉的代码即可,在显示或者消失的时候,判断一下当前Activity是否已经存在,存在的话再做其他相应逻辑。
当然,除此之外,像Dialog也是需要依赖当前Activity的,平时开发也要注意
在Android7.0上popwindow显示位置错误
这个版本需要弹一个Toast提示用户自定义一些个性需求,但是这个需求这个Toast需要点击事件,本来一开始想的很简单,new 一个TextView,然后给这个TextView设置点击监听即可,但是洋洋洒洒写完之后,并不生效。点击是没有反应的。所以,以后别想着用Toast来处理点击事件了。建议用Snackbar。
当然,我用的popwindow。
本来一切都ok的,但是在安卓7.0 测试的时候,出现了问题,本来显示的位置应该在屏幕的下方,但是在7.0手机上却显示在了上面。代码里已经 设置了Gravity属性,
popupWindow.showAtLocation(anchorView, Gravity.BOTTOM, 0, DPIUtil.dip2px(60));
其他手机没有该问题。
然后通过调查后发现,是在安卓7.0上,update()方法写错了,不过这个bug在安卓7.1已经修复
安卓7.0源代码如下:
public void update(int x, int y, int width, int height, boolean force) { if (width >= 0) { mLastWidth = width; setWidth(width); } if (height >= 0) { mLastHeight = height; setHeight(height); } if (!isShowing() || mContentView == null) { return; } final WindowManager.LayoutParams p = (WindowManager.LayoutParams) mDecorView.getLayoutParams(); boolean update = force; final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth; if (width != -1 && p.width != finalWidth) { p.width = mLastWidth = finalWidth; update = true; } final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight; if (height != -1 && p.height != finalHeight) { p.height = mLastHeight = finalHeight; update = true; } if (p.x != x) { p.x = x; update = true; } if (p.y != y) { p.y = y; update = true; } final int newAnim = computeAnimationResource(); if (newAnim != p.windowAnimations) { p.windowAnimations = newAnim; update = true; } final int newFlags = computeFlags(p.flags); if (newFlags != p.flags) { p.flags = newFlags; update = true; } final int newGravity = computeGravity(); if (newGravity != p.gravity) { p.gravity = newGravity; update = true; } int newAccessibilityIdOfAnchor = (mAnchor != null) ? mAnchor.get().getAccessibilityViewId() : -1; if (newAccessibilityIdOfAnchor != p.accessibilityIdOfAnchor) { p.accessibilityIdOfAnchor = newAccessibilityIdOfAnchor; update = true; } if (update) { setLayoutDirectionFromAnchor(); mWindowManager.updateViewLayout(mDecorView, p); }}
我们注意到 这个函数 computeGravity();跟踪进去看
private int computeGravity() { int gravity = Gravity.START | Gravity.TOP; if (mClipToScreen || mClippingEnabled) { gravity |= Gravity.DISPLAY_CLIP_VERTICAL; } return gravity;}
可以明显的看到,什么鬼,源码把我们设置的gravity属性给覆盖了。只有Gravity.START | Gravity.TOP这个属性了。所以问题显而易见了。。
解决方式主要有两种
1.不调用 update 方法即可
2.重写 update 方法
最简单是 dismiss,再调show
反射方法 把gravity那一段去掉
详见 http://www.jianshu.com/p/0df10893bf5b
当然,在我们的需求里面,只需要去掉update方法即可,没必要反射这么复杂。
popwindow在安卓6.0以下点击外部不消失情况
这个bug的出现还是很意外的,但是依然是google源码上的问题,我们知道,设置外部点击消失最简单的方式就是设置 setOutsideTouchable(true);在开发初期都是ok的,因为手机都是6.0版本,包括我的测试机。
但是交给测试在测试兼容性时,发现4.4版本的点击不能消失,很奇怪,以为是4.4独有的,后来发现5.0也有,只有6.0以上版本没有。所以解决问题,网上解决方案很多,但是很大一个原因都指向了popwindow的setBackgroundDrawable()方法。
是不是很奇怪,这个居然和点击消失挂钩。
那我们看一下源码吧。
安卓4.4源码:
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)这个方法,进去看一眼:
注意到红色方框区域,只有在backGround不为null的时候,才会创建一个叫PopViewContainer的类,而这个就是我们处理点击消失,按返回键消失的关键,跟踪源码如下:
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) { 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); } } }}
所以原因就很明显了,因为我并没有设置它的backGround,因为没这个必要,所以在安卓4.4上,就并没有创建这个PopViewContainer类,所以事件处理机制失效,也就不能点击外部消失了。
在安卓6.0上,代码做了修改:
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; }
可以看到,在安卓6.0上无论 backGround是否为null,都会创建一个createDecorView的类,追踪进去看:
private PopupDecorView createDecorView(View contentView) { final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); final int height; if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) { height = ViewGroup.LayoutParams.WRAP_CONTENT; } else { height = ViewGroup.LayoutParams.MATCH_PARENT; } final PopupDecorView decorView = new PopupDecorView(mContext); decorView.addView(contentView, ViewGroup.LayoutParams.MATCH_PARENT, height); decorView.setClipChildren(false); decorView.setClipToPadding(false); return decorView; }
会继续创建一个 PopupDecorView,而这个类的功能,就和上面的PopupViewContainer 事件处理机制一样了,所以,backGround是否有在安卓6.0以下不会影响点击外部是否消失逻辑。
哦了,这个版本遇到的问题先总结到这里
- popwindow 在安卓7.0上显示位置错误以及在6.0下点击外部不能消失的情况解析
- popwindow 点击外部消失
- PopWindow的showAsDropDown在7.0手机上显示位置错乱问题
- popwindow 在android 7.0位置错误
- popwindow在View的上,下,左,右 显示
- 关于安卓alertdialog你能用到的都在这里了(进出动画,显示位置,背景,设置内容,是否可以点击其他地方消失,屏幕不变暗)
- Popwindow在7.0系统上显示全屏
- popwindow有的手机点击外部不消失的处理办法
- 在手指按下的位置弹出PopWindow
- Android_百度地图API_通过点击地图在指定位置显示PopWindow
- PopupWindow在点击外部区域的时候消失(二)
- PopupWindow在安卓7.0及7.1系统上位置显示异常
- 安卓自定义dialog,改变其在主页面的位置和实现dialog上的按钮点击事件
- 解决popwindow在点击退出后背景透明度不能还原的问题
- Android开发之关于使用PopWindow点击外部不消失的解决实例
- 记录:点击popwindow外部不消失bug解决
- 判断鼠标或者手指是否点击在UI上(用于应对不能点击UI的情况)
- 安卓在锁屏的情况下唤醒屏幕
- C#消息队列(MQ)零基础从入门到实战演练
- layui源码详细分析之树形菜单
- WIFI开关用例(UIAutomator+python+Android)
- 【Quartz】定时任务(一)——Quartz.NET使用
- 实现lastindexof函数
- popwindow 在安卓7.0上显示位置错误以及在6.0下点击外部不能消失的情况解析
- bzoj 4627 回转寿司(权值线段树)
- linux-4-sendmail
- const关键字的作用
- 《探索推荐引擎内部的秘密》
- 二、c++中的字符串
- CSS自适应屏幕大小(手机端)
- 有序数组找到出现次数最高的元素和次数
- 成组链接法