Android7.0 PopupWindow的兼容問題

原文鏈接:http://www.cnblogs.com/popfisher/p/6434757.html

  Android7.0 中對 PopupWindow 這個常用的控件又做了一些改動,修復了以前遺留的一些問題的同時貌似又引入了一些問題,本文通過在7.0設備上實測並且結合源碼分析,帶你瞭解關於 PopupWindow 的相關改動。

  Android7.0 中下面兩個問題解決了,這裏強調一下,不是說從 Android7.0 開始才解決這兩個問題的,因爲具體版本細節沒去深究。可能在其他的某些版本下面的問題也是被解決了的。

  1. PopupWindow 不響應點擊外部消失和返回鍵消失的解決方法,博文地址:
    http://www.cnblogs.com/popfisher/p/5608717.html
  2. 不得不吐槽的 Android PopupWindow 的幾個痛點(實現帶箭頭的上下文菜單遇到的坑),博文地址:
    http://www.cnblogs.com/popfisher/p/5944054.html

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章