Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析 《四》-PopWindow

Android应用PopWindow窗口添加显示机制源码

PopWindow实质就是弹出式菜单,它与Dialag不同的地方是不会使依赖的Activity组件失去焦点(PopupWindow弹出后可以继续与依赖的Activity进行交互),Dialog却不能这样。同时PopupWindow与Dialog另一个不同点是PopupWindow是一个阻塞的对话框,如果你直接在Activity的onCreate等方法中显示它则会报错,所以PopupWindow必须在某个事件中显示地或者是开启一个新线程去调用。

说这么多还是直接看代码吧。
4-1 PopWindow窗口源码分析

依据PopWindow的使用,我们选择最常用的方式来分析,如下先看其中常用的一种构造函数:

public class PopupWindow {

    //我们只分析最常用的一种构造函数
    public PopupWindow(View contentView, int width, int height, boolean focusable) {
        if (contentView != null) {
            //获取mContext,contentView实质是View,View的mContext都是构造函数传入的,View又层级传递,所以最终这个mContext实质是Activity!!!很重要
            mContext = contentView.getContext();
            //获取Activity的getSystemService的WindowManager
            mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        //进行一些Window类的成员变量初始化赋值操作
        setContentView(contentView);
        setWidth(width);
        setHeight(height);
        setFocusable(focusable);
    }

}

可以看见,构造函数只是初始化了一些变量,看完构造函数继续看下PopWindow的展示函数,如下:

 public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
        ......
        //anchor是Activity中PopWindow准备依附的View,这个View的token实质也是Activity的Window中的token,也即Activity的token
        //第一步   初始化WindowManager.LayoutParams
        WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
        //第二步
        preparePopup(p);
        ......
        //第三步
        invokePopup(p);
    }

可以看见,当我们想将PopWindow展示在anchor的下方向(Z轴是在anchor的上面)旁边时经理了上面三步,我们一步一步来分析,先看第一步,源码如下:

 private WindowManager.LayoutParams createPopupLayout(IBinder token) {
        //实例化一个默认的WindowManager.LayoutParams,其中type=TYPE_APPLICATION
        WindowManager.LayoutParams p = new WindowManager.LayoutParams();
        //设置Gravity
        p.gravity = Gravity.START | Gravity.TOP;
        //设置宽高
        p.width = mLastWidth = mWidth;
        p.height = mLastHeight = mHeight;
        //依据背景设置format
        if (mBackground != null) {
            p.format = mBackground.getOpacity();
        } else {
            p.format = PixelFormat.TRANSLUCENT;
        }
        //设置flags
        p.flags = computeFlags(p.flags);
        //修改type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,mWindowLayoutType有初始值,type类型为子窗口
        p.type = mWindowLayoutType;
        //设置token为Activity的token
        p.token = token;
        ......
        return p;
    }

接着回到showAsDropDown方法看看第二步,如下源码:

 private void preparePopup(WindowManager.LayoutParams p) {
        ......
        //有无设置PopWindow的background区别
        if (mBackground != null) {
            ......
            //如果有背景则创建一个PopupViewContainer对象的ViewGroup
            PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
            PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, height
            );
            //把背景设置给PopupViewContainer的ViewGroup
            popupViewContainer.setBackground(mBackground);
            //把我们构造函数传入的View添加到这个ViewGroup
            popupViewContainer.addView(mContentView, listParams);
            //返回这个ViewGroup
            mPopupView = popupViewContainer;
        } else {
            //如果没有通过PopWindow的setBackgroundDrawable设置背景则直接赋值当前传入的View为PopWindow的View
            mPopupView = mContentView;
        }
        ......
    }

可以看见preparePopup方法的作用就是判断设置View,如果有背景则会在传入的contentView外面包一层PopupViewContainer(实质是一个重写了事件处理的FrameLayout)之后作为mPopupView,如果没有背景则直接用contentView作为mPopupView。我们再来看下这里的PopupViewContainer类,如下源码:

  private class PopupViewContainer extends FrameLayout {

        @Override
        protected int[] onCreateDrawableState(int extraSpace) {

        }

        @Override
        public boolean dispatchKeyEvent(KeyEvent 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) {

            if(xxx) {
                dismiss();
            }

        }

        @Override
        public void sendAccessibilityEvent(int eventType) {

        }
    }

可以看见,这个PopupViewContainer是一个PopWindow的内部私有类,它继承了FrameLayout,在其中重写了Key和Touch事件的分发处理逻辑。同时查阅PopupView可以发现,PopupView类自身没有重写Key和Touch事件的处理,所以如果没有将传入的View对象放入封装的ViewGroup中,则点击Back键或者PopWindow以外的区域PopWindow是不会消失的(其实PopWindow中没有向Activity及Dialog一样new新的Window,所以不会有新的callback设置,也就没法处理事件消费了)。

接着继续回到showAsDropDown方法看看第三步,如下源码:

private void invokePopup(WindowManager.LayoutParams p) {
        if (mContext != null) {
            p.packageName = mContext.getPackageName();
        }
        mPopupView.setFitsSystemWindows(mLayoutInsetDecor);
        setLayoutDirectionFromAnchor();
        mWindowManager.addView(mPopupView, p);
    }

可以看见,这里使用了Activity的WindowManager将我们的PopWindow进行了显示。

到此可以发现,PopWindow的实质无非也是使用WindowManager的addView、updateViewLayout、removeView进行一些操作展示。与Dialog不同的地方是没有新new Window而已(也就没法设置callback,无法消费事件,也就是前面说的PopupWindow弹出后可以继续与依赖的Activity进行交互的原因)。

到此PopWindw的窗口加载显示机制就分析完毕了,接下来进行总结与应用开发技巧提示。
4-2 PopWindow窗口源码分析总结及应用开发技巧提示

通过上面分析可以发现总结如下图:

这里写图片描述

可以看见,PopWindow完全使用了Activity的Window与WindowManager,相对来说比较简单容易记理解。

再来看一个开发技巧:

如果设置了PopupWindow的background,则点击Back键或者点击PopupWindow以外的区域时PopupWindow就会dismiss;如果不设置PopupWindow的background,则点击Back键或者点击PopupWindow以外的区域PopupWindow不会消失。

【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重劳动成果】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值