Android中PopupWindow 在 Android N(7.0) 的兼容性问题
2017-08-04 16:30
567 查看
转载地址:http://www.jianshu.com/p/0df10893bf5b
老早QA就提了个bug,说我们的popupWindow在android N (7.0)系统展示不对。
然后我今天有空就把这个bug修了,没明白google为啥这次这样改PopupWindow,可能是他们的bug,下面详细看看这个是什么bug。
很明显这个bug是和我们设置了Gravity有关。
展示popupWindow的函数有两个,showAtLocation 和 update。
重点看了那两个函数的API 24 和 API 23 的区别。
有个 computeGravity 函数,我们再看看
噗,我们发现,我们之前设置的gravity被这个函数执行之后覆盖了。。
我忽然觉得估计是Google的大牛自测的时候写死变量好调试,最后发布的时候忘记改了。。
问题定位到了,符合2个条件:
popupWindow 在 show 的时候定义了 Gravity,不是 Gravity.START | Gravity.TOP。
PopupWindow 调用了update。
不调用 update 方法即可
重写 update 方法
最简单是 dismiss,再调show
反射方法 把gravity那一段去掉
我提供反射方法的办法
但是我这个方法有缺陷的,反射的办法会遇到Google Api如果把变量名改了,那就直接无效了。
但是要解决这种系统bug也只能做API适配了。
欢迎围观 我的博客
作者:Kinva
链接:http://www.jianshu.com/p/0df10893bf5b
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
老早QA就提了个bug,说我们的popupWindow在android N (7.0)系统展示不对。
然后我今天有空就把这个bug修了,没明白google为啥这次这样改PopupWindow,可能是他们的bug,下面详细看看这个是什么bug。
兼容性现象
popupWindow设置了居中或者底部对齐,但是在7.0机器是跑到顶部。很明显这个bug是和我们设置了Gravity有关。
展示popupWindow的函数有两个,showAtLocation 和 update。
重点看了那两个函数的API 24 和 API 23 的区别。
源码分析
通过源码分析发现,在update函数里有一个和gravity相关的地方,很明显是个bug。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被这个函数执行之后覆盖了。。
我忽然觉得估计是Google的大牛自测的时候写死变量好调试,最后发布的时候忘记改了。。
问题定位到了,符合2个条件:
popupWindow 在 show 的时候定义了 Gravity,不是 Gravity.START | Gravity.TOP。
PopupWindow 调用了update。
解决方案
两种方案不调用 update 方法即可
重写 update 方法
最简单是 dismiss,再调show
反射方法 把gravity那一段去掉
我提供反射方法的办法
但是我这个方法有缺陷的,反射的办法会遇到Google Api如果把变量名改了,那就直接无效了。
但是要解决这种系统bug也只能做API适配了。
package cc.kinva.widget; import android.content.Context; import android.os.Build; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.view.WindowManager; import android.widget.PopupWindow; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * Created by Kinva on 16/9/23. */ public class FixedPopupWindow extends PopupWindow { public FixedPopupWindow(Context context) { super(context); } public FixedPopupWindow(Context context, AttributeSet attrs) { super(context, attrs); } public FixedPopupWindow(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public FixedPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } public FixedPopupWindow(View contentView) { super(contentView); } public FixedPopupWindow() { super(); } public FixedPopupWindow(int width, int height) { super(width, height); } public FixedPopupWindow(View contentView, int width, int height, boolean focusable) { super(contentView, width, height, focusable); } public FixedPopupWindow(View contentView, int width, int height) { super(contentView, width, height); } @Override public void update(int x, int y, int width, int height, boolean force) { if (Build.VERSION.SDK_INT < 24) { super.update(x, y, width, height, force); return; } if (width >= 0) { setParam("mLastWidth", width); setWidth(width); } if (height >= 0) { setParam("mLastHeight", height); setHeight(height); } Object obj = getParam("mContentView"); View mContentView = null; if (obj instanceof View) { mContentView = (View) obj; } if (!isShowing() || mContentView == null) { return; } obj = getParam("mDecorView"); View mDecorView = null; if (obj instanceof View) { mDecorView = (View) obj; } final WindowManager.LayoutParams p = (WindowManager.LayoutParams) mDecorView.getLayoutParams(); boolean update = force; obj = getParam("mWidthMode"); int mWidthMode = obj != null ? (Integer) obj : 0; obj = getParam("mLastWidth"); int mLastWidth = obj != null ? (Integer) obj : 0; final int finalWidth = mWidthMode < 0 ? mWidthMode : mLastWidth; if (width != -1 && p.width != finalWidth) { p.width = finalWidth; setParam("mLastWidth", finalWidth); update = true; } obj = getParam("mHeightMode"); int mHeightMode = obj != null ? (Integer) obj : 0; obj = getParam("mLastHeight"); int mLastHeight = obj != null ? (Integer) obj : 0; final int finalHeight = mHeightMode < 0 ? mHeightMode : mLastHeight; if (height != -1 && p.height != finalHeight) { p.height = finalHeight; setParam("mLastHeight", finalHeight); update = true; } if (p.x != x) { p.x = x; update = true; } if (p.y != y) { p.y = y; update = true; } obj = execMethod("computeAnimationResource"); final int newAnim = obj == null ? 0 : (Integer) obj; if (newAnim != p.windowAnimations) { p.windowAnimations = newAnim; update = true; } obj = execMethod("computeFlags", new Class[]{int.class}, new Object[]{p.flags}); final int newFlags = obj == null ? 0 : (Integer) obj; if (newFlags != p.flags) { p.flags = newFlags; update = true; } if (update) { execMethod("setLayoutDirectionFromAnchor"); obj = getParam("mWindowManager"); WindowManager mWindowManager = obj instanceof WindowManager ? (WindowManager) obj : null; if (mWindowManager != null) { mWindowManager.updateViewLayout(mDecorView, p); } } } /** * 反射获取对象 * @param paramName * @return */ private Object getParam(String paramName) { if (TextUtils.isEmpty(paramName)) { return null; } try { Field field = PopupWindow.class.getDeclaredField(paramName); field.setAccessible(true); return field.get(this); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 反射赋值对象 * @param paramName * @param obj */ private void setParam(String paramName, Object obj) { if (TextUtils.isEmpty(paramName)) { return; } try { Field field = PopupWindow.class.getDeclaredField(paramName); field.setAccessible(true); field.set(this, obj); } catch (Exception e) { e.printStackTrace(); } } /** * 反射执行方法 * @param methodName * @param args * @return */ private Object execMethod(String methodName, Class[] cls, Object[] args) { if (TextUtils.isEmpty(methodName)) { return null; } try { Method method = getMethod(PopupWindow.class, methodName, cls); method.setAccessible(true); return method.invoke(this, args); } catch (Exception e) { e.printStackTrace(); } return null; } /** * 利用递归找一个类的指定方法,如果找不到,去父亲里面找直到最上层Object对象为止。 * * @param clazz * 目标类 * @param methodName * 方法名 * @param classes * 方法参数类型数组 * @return 方法对象 * @throws Exception */ private Method getMethod(Class clazz, String methodName, final Class[] classes) throws Exception { Method method = null; try { method = clazz.getDeclaredMethod(methodName, classes); } catch (NoSuchMethodException e) { try { method = clazz.getMethod(methodName, classes); } catch (NoSuchMethodException ex) { if (clazz.getSuperclass() == null) { return method; } else { method = getMethod(clazz.getSuperclass(), methodName, classes); } } } return method; } }
欢迎围观 我的博客
作者:Kinva
链接:http://www.jianshu.com/p/0df10893bf5b
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关文章推荐
- Android 开发 Tip 18 -- PopupWindow 在 Android N(7.0)及以上 的兼容性问题
- PopupWindow 在 Android N(7.0) 的兼容性问题
- 解决在Android 7.0上PopupWindow.showAsDropDown不起作用的问题
- Android 7.0 PopupWindow弹出位置的适配问题
- Android PopupWindow 7.0之后出现的问题
- Android 7.0 popupwindow位置问题
- PopupWindow 在 Android N(7.0) 的兼容性问题
- PopupWindow的showAsDropDown位置问题 Android7.0
- Android 6.0+ RecyclerView嵌套在ScrollView显示不全以及Android 7.0+ PopupWindow位置显示不对的问题解决
- Android 7.0 PopupWindow 又引入新的问题,Google工程师也不够仔细么
- Android 7.0 PopupWindow 又引入新的问题,Google工程师也不够仔细么
- android之Notification版本兼容性问题
- android中popupwindow布局的一些小问题
- Android PopupWindow 与 软键盘 的遮挡问题解决
- Android开发之旅一PopupWindow写的一个基类和PopupWindow中EditText输入法的问题
- PopupWindow在android2.3不显示文字问题
- android调用高版本api函数的兼容性问题
- android截图兼容性问题解决
- android调用高版本api函数的兼容性问题
- [置顶] android之Notification版本兼容性问题