您的位置:首页 > 移动开发 > Android开发

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。

兼容性现象

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

來源:简书

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: