PopUpWindow在6.0以上返回监听无效的问题

我们在开发中经常使用到PopUpWindow,有时我们需要在弹出popupwindow之后,点击手机的返回按键,在popupwindow dismiss之前做一些其他的事情,比如动画效果,以下为标准写法

            View view= LayoutInflater.from(MainActivity.this).inflate(R.layout.pop_layout,null);
            view.setFocusable(true);
            view.setFocusableInTouchMode(true);
            final PopupWindow popupWindow=new PopupWindow(view, ViewGroup.LayoutParams.WRAP_CONTENT,         
            ViewGroup.LayoutParams.WRAP_CONTENT);
            popupWindow.setFocusable(true);
            popupWindow.showAtLocation(view, Gravity.BOTTOM,0,0);
            view.setOnKeyListener(new View.OnKeyListener() {
                @Override
                public boolean onKey(View v, int keyCode, KeyEvent event) {
                    if(keyCode == KeyEvent.KEYCODE_BACK){
                        //do something
                        popupWindow.dismiss();
                        return true;
                    }
                    return false;
                }
            });

这代码在6.0以下系统没有问题,在6.0以上系统就出现了拦截不到手机返回按键事件的问题了,onKey不会执行。查看源码,我发现了问题所在,他们都会执行preparePopup方法,而preparePopup有所区别, 以下为Android 5.0 popwindow的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.");
    }

    if (mBackground != null) {
        final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
        int height = ViewGroup.LayoutParams.MATCH_PARENT;
        if (layoutParams != null &&
                layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
            height = ViewGroup.LayoutParams.WRAP_CONTENT;
        }

        // when a background is available, we embed the content view
        // within another view that owns the background drawable
        PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
        PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, height
        );
        popupViewContainer.setBackground(mBackground);
        popupViewContainer.addView(mContentView, listParams);

        mPopupView = popupViewContainer;
    } else {
        mPopupView = mContentView;
    }

    mPopupView.setElevation(mElevation);
    mPopupViewInitialLayoutDirectionInherited =
            (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
    mPopupWidth = p.width;
    mPopupHeight = p.height;
}

我们没有设置backgroud所以mBackground为null ,执行 mPopupView = mContentView代码,以下是6.0部分源码

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

我们发现这里无论有没有设置background,都会执行createDecorView方法,我们再看看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;
}

这个方法对contentView进行了包装,我们再看看PopupDecorView源码

 private class PopupDecorView extends FrameLayout {
    /** Runnable used to clean up listeners after exit transition. */
    private Runnable mCleanupAfterExit;

    public PopupDecorView(Context context) {
        super(context);
    }

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

}

问题就在这,这里的PopupDecorView是FrameLayout级别的,这里设置了dispatchKeyEvent,并且 return true,消费了点击事件,所以不会分发给我们设置的onkeyListner,因此我们设置监听无效,目前没有找到好的解决办法,但好在不影响功能。

你可能感兴趣的:(PopUpWindow在6.0以上返回监听无效的问题)