PopupWindow 在 Android N(7.0) 的兼容性问题

兼容性现象

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;
    }
}

转载:http://kinva.cc/2016/09/23/popupwindow-update-bug-in-android-N/
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高能:Go语言具有出色的能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高能的服务器和分布式系统时具有天然的优势。 安全:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值