PopupWindow常见的问题解析

setOutsideTouchable

setOutsideTouchable(true),点击非PopupWindow视图区域,直接隐藏PopupWindow。调用setOutsideTouchable(true)会使得点击非PopupWindow视图区域,PopupWindow能够接收到ACTION_OUTSIDE事件,接收到这个事件后执行dismiss。但是在5.0以下需要调用setBackground给PopupWindow设置一个非空的背景才会起作用。

小于5.0分析

5.0以下的PopupWindow,当mBackground为非null时,才会创建PopupViewContainer来封装contentView,而ACTION_OUTSIDE的处理正是由其来处理。点击查看4.4源代码

    private void preparePopup(WindowManager.LayoutParams p) {
           ......
            if (mBackground != null) {
                ......
                PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
                ......
                mPopupView = popupViewContainer;
            } else {
                mPopupView = mContentView;
            }
           ......
    }

    private class PopupViewContainer extends FrameLayout {
        ......
        @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);
            }
        }
        ......
    }

大于等于5.0分析

和小于5.0分析类似,大于等于5.0的PopupWindow,不管mBackground是否为null,都会创建PopupDecorView来封装contentView。PopupDecorView会处理ACTION_OUTSIDE事件。点击查看5.0源代码

    ......
    private void preparePopup(WindowManager.LayoutParams p) {
        ......
        // 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);
        ......
    }
    ......
    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;
    }
    ......
    private class PopupDecorView extends FrameLayout {
    ......
        @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);
            }
        }
    ......
}

setFocusable

调用setFocusable(true),按back键先隐藏PopupWindow而不是整个Activity。setFoucusable(true)将PopupWindow的mFoucusable设置为true。在PopupWindow显示时如果mFoucusable为false,则会将FLAG_NOT_FOCUSABLE设置到Window上,导致系统按键事件不会派发给PopupWindow。mFoucusable默认为false,所以这就是必须调用setFocusable(true)的原因。

你可能感兴趣的:(PopupWindow常见的问题解析)