Android 7.0 PopupWindow 又引入新的问题,Google工程师也不够仔细么

Android7.0 PopupWindow的兼容问题

Android7.0 中对 PopupWindow 这个常用的控件又做了一些改动,修复了以前遗留的一些问题的同时貌似又引入了一些问题,本文通过在7.0设备上实测并且结合源码分析,带你了解关于 PopupWindow 的相关改动。

Android7.0 中下面两个问题解决了,这里强调一下,不是说从 Android7.0 开始才解决这两个问题的,因为具体版本细节没去深究。可能在其他的某些版本下面的问题也是被解决了的。

  1. PopupWindow 不响应点击外部消失和返回键消失的解决方法,博文地址: https://cloud.tencent.com/developer/article/1013227
  2. 不得不吐槽的 Android PopupWindow 的几个痛点(实现带箭头的上下文菜单遇到的坑),博文地址: https://cloud.tencent.com/developer/article/1013242

Android7.0 中又引入了新的问题(这就非常的尴尬了)

  1. 调用 update 方法,PopupWindowGravity 会改变

从源码看7.0怎么解决遗留问题的

  解决 PopupWindow 不响应点击外部消失和返回键消失的问题,我们是通过自己设置一个背景。Android7.0 中不设置背景也是可以的,那么它的代码肯定做了处理。从 api24 的源码中找到 PopupWindow.java 文件,我找到里面的 preparePopup 方法如下:

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.
    p.setSurfaceInsets(mBackgroundView, true /*manual*/, true /*preservePrevious*/);

    mPopupViewInitialLayoutDirectionInherited =
            (mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
}

  重点只需要看 mDecorView = createDecorView(mBackgroundView); 可以看到不管 mBackground 变量是否为空,最终都执行了这句代码,这句代码会多加一层 ViewGroupmBackgroundView 包进去了,里面应该包含了对返回键的处理逻辑,我们再看看 createDecorView 方法源码:

private PopupDecorView createDecorView(View contentView) {
    final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
    final int height;
    if (layoutParams != null && layoutParams.height == WRAP_CONTENT) {
        height = WRAP_CONTENT;
    } else {
        height = MATCH_PARENT;
    }

    final PopupDecorView decorView = new PopupDecorView(mContext);
    decorView.addView(contentView, MATCH_PARENT, height);
    decorView.setClipChildren(false);
    decorView.setClipToPadding(false);

    return decorView;
}

createDecorView 里面还是没有直接看出对事件的处理,但是里面有个 PopupDecorView 类,应该在里面了吧,继续看:

    private class PopupDecorView extends FrameLayout {
    //......有代码被省略

    @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) {
                final KeyEvent.DispatcherState state = getKeyDispatcherState();
                if (state != null) {
                    state.startTracking(event, this);
                }
                return true;
            } else if (event.getAction() == KeyEvent.ACTION_UP) {
                final 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 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);
        }
    }

    //......有代码被省略
}

  从上面的代码中我们看到了KeyEvent.KEYCODE_BACKMotionEvent.ACTION_OUTSIDE,没错这里有对返回键和其他事件的处理。

  至于怎么解决 showAsDropDown 方法弹出位置不对的问题,也就是上文中描述的第二个问题,本文就不贴源码了,感兴趣的可以下载源码去看看,本文只是提供一种解决问题的思路,希望大家能从源码中找到解决问题的办法,这才是作者希望达到的效果。 文章末尾会给出 Android7.0 PopupWindow.java 的 java 文件。

Android7.0引入的新问题

  调用 update 方法时,PopupWindowGravity 会改变,导致位置发生了改变,具体看下图:

showAtLocation传入Gravity.Bottom:从屏幕底部对齐弹出

调用update方法更新第5点中弹出PopupWindow,发现PopupWindow的Gravity发生了改变

关于这个问题还有篇文章可以参考, http://www.jianshu.com/p/0df10893bf5b

Android7.0 PopupWindow其他改动点,与Android5.1的对比

主界面

1. PopupWindow高宽都设置为match_parent:7.0(左边)从屏幕左上角弹出,5.1(右边)从anchorView下方弹出

2. 宽度wrap_content-高度match_parent:7.0(左边)从屏幕左上角弹出,5.1(右边)从anchorView下方弹出

3. 宽度match_parent-高度wrap_content:都从anchorView下方弹出

4. 宽度wrap_content-高度大于anchorView到屏幕底部的距离:7.0与5.1都从anchorView上方弹出,与anchorView左对齐

源码地址

Github工程地址,收录了 PopupWindow 相关使用问题: https://github.com/PopFisher/SmartPopupWindow

Android 7.0 PopupWindow.java 文件: https://github.com/PopFisher/SmartPopupWindow/blob/master/sourcecode/PopupWindow(7.0).java

总结

Android PopupWindow 这个控件 Google 一直没有优化好,使用时需要参考我之前的几篇文章。本文是希望读者善于从源码的角度去分析和解决问题,加深自己对源码的理解,对问题的理解,这样印象要深刻一些。

  本来2017年回来还没有时间写写文章,这篇文章也是巧合,同事在 Android7.0 中发现 PopupWindow 使用上有 bug,所以我就借此机会研究一下,虽然知识点简单,但是也花费了几个小时的时间整理出这样一篇文章。如果读者觉得有用,别忘记点击推荐哦,总之也算是开了一个好头吧,以后还是会坚持每个月写些文章出来分享。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java后端技术

欠的债,这一次都还给你们

  前段时间,写的一篇关于博客园主题皮肤分享的文章,一经发出便受到了极大的热捧,后来很多网友留言说,想要让我分享现在的模板,毕竟现在这个模板已经比之前分享的那个...

713
来自专栏李蔚蓬的专栏

Android实战_note1(MyMirror_一款小型摄像处理的App)

1.1 Activity.java全文: 注意代码中的注释,其中 handler.sendEmptyMessageDelayed(1,3000);...

572
来自专栏我和未来有约会

Silverlight类库介绍-FJCore

Silverlight类库介绍-FJCore FJCore是一个图片编码类库(目前只有对JPEG格式的支持)。 项目地址:http://code.google....

1718
来自专栏技术小黑屋

Package Stopped State Since Android 3.1

Since Android 3.1, Android has introduced a LaunchControl mechanism. It’s call S...

611
来自专栏青蛙要fly的专栏

项目需求讨论-APP中提交信息及编辑信息界面及功能

好久好久没写文章了,这次我们来讨论下一些具有填写很多资料的界面,或者详情编辑界面等如何做起来更方便。 (PS:我写的可能不好,希望大家不好喷,哈哈,可以留言)

812
来自专栏项勇

笔记27 | WindowManager实现悬浮窗口总结

1906
来自专栏hotqin888的专栏

golang-fullcalendar,engineercms完善日历事件-支持拖曳drop,改变时间resize

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hotqin888/article/det...

802
来自专栏Hongten

pygame系列_原创百度随心听音乐播放器_完整版

 1.当鼠标移动到黄色圆区域,会展示出我的相片和'Yes,You are Luck:)'字样

783
来自专栏吴小龙同學

Android 从 Web 唤起 APP

前言 ? 知乎在手机浏览器打开,会有个 App 内打开的按钮,点击直接打开且跳转到该详情页,是不是有点神奇,是如何做到的呢? 效果预览 ? Uri Sche...

31311
来自专栏项勇

笔记54 | 管理系统UI(二)

1454

扫码关注云+社区