原文鏈接:http://www.cnblogs.com/popfisher/p/6434757.html
Android7.0
中對 PopupWindow
這個常用的控件又做了一些改動,修復了以前遺留的一些問題的同時貌似又引入了一些問題,本文通過在7.0設備上實測並且結合源碼分析,帶你瞭解關於 PopupWindow
的相關改動。
Android7.0
中下面兩個問題解決了,這裏強調一下,不是說從 Android7.0
開始才解決這兩個問題的,因爲具體版本細節沒去深究。可能在其他的某些版本下面的問題也是被解決了的。
PopupWindow
不響應點擊外部消失和返回鍵消失的解決方法,博文地址:
http://www.cnblogs.com/popfisher/p/5608717.html- 不得不吐槽的
Android PopupWindow
的幾個痛點(實現帶箭頭的上下文菜單遇到的坑),博文地址:
http://www.cnblogs.com/popfisher/p/5944054.html
Android7.0 中又引入了新的問題(這就非常的尷尬了)
- 調用
update
方法,PopupWindow
的Gravity
會改變
從源碼看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
變量是否爲空,最終都執行了這句代碼,這句代碼會多加一層 ViewGroup
把 mBackgroundView
包進去了,裏面應該包含了對返回鍵的處理邏輯,我們再看看 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_BACK
和 MotionEvent.ACTION_OUTSIDE
,沒錯這裏有對返回鍵和其他事件的處理。
至於怎麼解決 showAsDropDown
方法彈出位置不對的問題,也就是上文中描述的第二個問題,本文就不貼源碼了,感興趣的可以下載源碼去看看,本文只是提供一種解決問題的思路,希望大家能從源碼中找到解決問題的辦法,這纔是作者希望達到的效果。 文章末尾會給出 Android7.0 PopupWindow.java
的 java 文件。
Android7.0引入的新問題
調用 update
方法時,PopupWindow
的 Gravity
會改變,導致位置發生了改變,具體看下圖:
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