Android 7.0以上(包含8.0), 或者有虛擬按鍵,popupWindow彈窗位置異常的終極解決方案
阿新 • • 發佈:2019-01-02
問題描述
前段時間發現Popupwindow在8.0的手機上顯示成全屏了,搜了下發現7.0以上就有這個問題了,好久沒寫Popwindow了,才知道(尷尬)。於是總結了在以下情況可能出問題:
- 當設定PopupWindow 的高度為 MATCH_PARENT,呼叫 showAsDropDown(View anchor) 時,在 7.0 之前,會在 anchor 下邊緣到螢幕底部之間顯示 PopupWindow;而在 7.0系統上(包括7.1, 8.0)的 PopupWindow 會佔據整個螢幕(除狀態列之外)。
- 當設定PopupWindow 的高度為 WRAP_CONTENT,呼叫 showAsDropDown(View anchor) 時,沒有相容性問題。
- 當設定PopupWindow 的高度為自定義的值height,呼叫 showAsDropDown(View anchor)時, 如果 height > anchor 下邊緣與螢幕底部的距離, 則還是會出現7.0以上顯示異常的問題;否則,不會出現該問題。
問題解決
好了,現在我們知道問題出現的情況了,上網搜了很多解決方案,發現都不能完美解決問題,如以下程式碼:
if (Build.VERSION.SDK_INT >= 24) {
int[] location = new int[2];
anchor.getLocationOnScreen( location);
// 7.1 版本處理
if (Build.VERSION.SDK_INT == 25) {
WindowManager windowManager = (WindowManager) pw.getContentView().getContext().getSystemService(Context.WINDOW_SERVICE);
if (windowManager != null) {
int screenHeight = windowManager.getDefaultDisplay(). getHeight();
// PopupWindow height for match_parent, will occupy the entire screen, it needs to do special treatment in Android 7.1
pw.setHeight(screenHeight - location[1] - anchor.getHeight() - yoff);
}
}
pw.showAtLocation(anchor, Gravity.NO_GRAVITY, xoff, location[1] + anchor.getHeight() + yoff);
} else {
pw.showAsDropDown(anchor, xoff, yoff);
}
或者這種程式碼:
public static void showAsDropDown(final PopupWindow pw, final View anchor, final int xoff, final int yoff) {
if (Build.VERSION.SDK_INT >= 24) {
Rect visibleFrame = new Rect();
anchor.getGlobalVisibleRect(visibleFrame);
int height = anchor.getResources().getDisplayMetrics().heightPixels - visibleFrame.bottom;
pw.setHeight(height);
pw.showAsDropDown(anchor, xoff, yoff);
} else {
pw.showAsDropDown(anchor, xoff, yoff);
}
}
這兩段程式碼的主要問題是,沒有考慮虛擬按鍵的情況,螢幕的高度算的不一定對,或者有虛擬按鍵,虛擬按鍵是否顯示都會影響螢幕的可用高度。所以問題的關鍵是算出螢幕真實的可用高度。
為了方便以後複用,我們重新定義一個PopupWindowCompat繼承自PopupWindow,具體程式碼如下,主要是綜合考慮以上三種情況,和算出螢幕的真實可用高度,註釋已經寫的很清楚了吧,我就省點字了(偷懶)。
獲取螢幕真實總高度:
public static int getScreenHeight(Activity activity) {
if (activity == null) {
return 0;
}
Display display = activity.getWindowManager().getDefaultDisplay();
int realHeight = 0;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
final DisplayMetrics metrics = new DisplayMetrics();
display.getRealMetrics(metrics);
realHeight = metrics.heightPixels;
} else {
try {
Method mGetRawH = Display.class.getMethod("getRawHeight");
realHeight = (Integer) mGetRawH.invoke(display);
} catch (Exception e) {
e.printStackTrace();
}
}
return realHeight;
}
檢測是否具有底部導航欄
private static boolean checkDeviceHasNavigationBar(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
WindowManager windowManager = activity.getWindowManager();
Display display = windowManager.getDefaultDisplay();
DisplayMetrics realDisplayMetrics = new DisplayMetrics();
display.getRealMetrics(realDisplayMetrics);
int realHeight = realDisplayMetrics.heightPixels;
int realWidth = realDisplayMetrics.widthPixels;
DisplayMetrics displayMetrics = new DisplayMetrics();
display.getMetrics(displayMetrics);
int displayHeight = displayMetrics.heightPixels;
int displayWidth = displayMetrics.widthPixels;
return (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0;
} else {
boolean hasNavigationBar = false;
Resources resources = activity.getResources();
int id = resources.getIdentifier("config_showNavigationBar", "bool", "android");
if (id > 0) {
hasNavigationBar = resources.getBoolean(id);
}
try {
Class systemPropertiesClass = Class.forName("android.os.SystemProperties");
Method m = systemPropertiesClass.getMethod("get", String.class);
String navBarOverride = (String) m.invoke(systemPropertiesClass, "qemu.hw.mainkeys");
if ("1".equals(navBarOverride)) {
hasNavigationBar = false;
} else if ("0".equals(navBarOverride)) {
hasNavigationBar = true;
}
} catch (Exception e) {
}
return hasNavigationBar;
}
}
有底部導航欄時,底部導航欄是否可見:
private static boolean isNavigationBarVisible(Activity activity) {
boolean show = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Display display = activity.getWindow().getWindowManager().getDefaultDisplay();
Point point = new Point();
display.getRealSize(point);
View decorView = activity.getWindow().getDecorView();
Configuration conf = activity.getResources().getConfiguration();
if (Configuration.ORIENTATION_LANDSCAPE == conf.orientation) {
View contentView = decorView.findViewById(android.R.id.content);
show = (point.x != contentView.getWidth());
} else {
Rect rect = new Rect();
decorView.getWindowVisibleDisplayFrame(rect);
show = (rect.bottom != point.y);
}
}
return show;
}
獲取底部導航欄真實高度:
/**
* 獲取當前底部導航欄高度(隱藏後高度為0)
*
* @param activity
* @return
*/
public static int getCurrentNavigationBarHeight(Activity activity) {
int navigationBarHeight = 0;
Resources resources = activity.getResources();
int resourceId = resources.getIdentifier(isPortrait(activity) ? "navigation_bar_height" : "navigation_bar_height_landscape", "dimen", "android");
if (resourceId > 0 && checkDeviceHasNavigationBar(activity) && isNavigationBarVisible(activity)) {
navigationBarHeight = resources.getDimensionPixelSize(resourceId);
}
return navigationBarHeight;
}
獲取螢幕可用高度:
/**
* 獲取可用螢幕高度,排除虛擬鍵
*
* @param context 上下文
* @return 返回高度
*/
public static int getContentHeight(Activity context) {
int contentHeight = getScreenHeight(context) - getCurrentNavigationBarHeight(context);
return contentHeight;
}
自定義的PopupWindowCompat的主要程式碼如下:
@Override
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
// 7.0 以下或者高度為WRAP_CONTENT, 預設顯示
if (Build.VERSION.SDK_INT < 24 || getHeight() == ViewGroup.LayoutParams.WRAP_CONTENT) {
super.showAsDropDown(anchor, xoff, yoff, gravity);
} else {
if (getContentView().getContext() instanceof Activity) {
Activity activity = (Activity) getContentView().getContext();
int screenHeight;
// 獲取螢幕真實高度, 減掉虛擬按鍵的高度
screenHeight = OsUtils.getContentHeight(activity);
int[] location = new int[2];
// 獲取控制元件在螢幕的位置
anchor.getLocationOnScreen(location);
// 算出popwindow最大高度
int maxHeight = screenHeight - location[1] - anchor.getHeight();
// popupwindow 有具體的高度值,但是小於anchor下邊緣與螢幕底部的距離, 正常顯示
if(getHeight() > 0 && getHeight() < maxHeight){
super.showAsDropDown(anchor, xoff, yoff, gravity);
}else {
// match_parent 或者 popwinddow的具體高度值大於anchor下邊緣與螢幕底部的距離, 都設定為最大可用高度
setHeight(maxHeight);
super.showAsDropDown(anchor, xoff, yoff, gravity);
}
}
}
}
好了,完美解決,以後可以愉快的使用Popwindow了,如果想看完整程式碼和例子可以移步https://github.com/wang0826jj/PopupWindowCompat