Android劉海屏適配精煉詳解
一、前期基礎知識儲備
話不多說,這麼多劉海屏手機今年集中爆發,所以儘管劉海屏不好看,但是還是要適配。
2017年蘋果X開啟了劉海屏時代,2018年集中爆發,紛紛採取劉海屏這一策略來實現全面屏的概念,所以Android手機對於劉海屏的適配也是比較重要的。所謂適配劉海屏,其實就是處理與劉海齊平的手機螢幕部分,這也是所有劉海屏手機系統自帶的一個可選項:是否顯示劉海屏,以華為劉海屏為例,是否顯示劉海屏前後效果如下:
從上面的圖中我們可以發現這幾個重要的適配資訊:
①與劉海屏齊平的手機螢幕部分實際上是手機的狀態列;
②顯示劉海屏,劉海部分顯示的狀態背景色為APP應用背景色,狀態列文字圖示部分變為黑色;
③不顯示劉海屏,則劉海部分顯示的狀態列為手機原始狀態列,電量標誌、事件、運營商資訊都是白字;
所以適配劉海屏的關鍵在於:
①判斷是否是劉海屏;
②如果是劉海屏,則顯示的狀態列顏色變為APP應用本身的背景色;
③其次狀態列中的圖示、文字等資訊是否需要變色(深色背景色時定為白色,淺色背景色時定為黑色)
二、上程式碼 具體實現
1)判斷是否是劉海屏手機 工具類judgeNotchUtils
/** * 判斷是否是劉海屏 寫在BaseActivity onCreate()方法中 * 國內主流手機小米 華為 VIVO OPPO劉海屏判斷 * @return */ public static boolean hasNotchScreen(Activity activity){ if (getInt("ro.miui.notch",activity) == 1 || hasNotchInHuawei(activity) || hasNotchInVivo(activity) || hasNotchInOppo(activity) || hasNotchInXiaomi(activity)){ return true; } return false; } /** * Android P 版本判斷 需要應用的CompileSDKVersion設為28 * 其他劉海屏手機判斷 * @param activity * @return */ public static DisplayCutout isAndroidP(Activity activity){ View decorView = activity.getWindow().getDecorView(); if (decorView != null && android.os.Build.VERSION.SDK_INT >= 28){ WindowInsets windowInsets = decorView.getRootWindowInsets(); if (windowInsets != null) return windowInsets.getDisplayCutout(); } return null; } /** * 小米劉海屏判斷 * @return 0 if it is not notch ; return 1 means notch * @throws IllegalArgumentException if the key exceeds 32 characters */ public static int getInt(String key,Activity activity) { int result = 0; if (isXiaomi()){ try { ClassLoader classLoader = activity.getClassLoader(); @SuppressWarnings("rawtypes") Class SystemProperties = classLoader.loadClass("android.os.SystemProperties"); @SuppressWarnings("rawtypes") Class[] paramTypes = new Class[2]; paramTypes[0] = String.class; paramTypes[1] = int.class; Method getInt = SystemProperties.getMethod("getInt", paramTypes); Object[] params = new Object[2]; params[0] = new String(key); params[1] = new Integer(0); result = (Integer) getInt.invoke(SystemProperties, params); } catch (Exception e) { return result; } } return result; } public static boolean hasNotchInXiaomi(Context context) { if (isXiaomi()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { int result = 0; int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { result = context.getResources().getDimensionPixelSize(resourceId); } if (result > 0) { return true; } else { return false; } } } return false; } /** * 華為劉海屏判斷 * @return */ public static boolean hasNotchInHuawei(Context context) { boolean hasNotch = false; try { ClassLoader cl = context.getClassLoader(); Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil"); Method hasNotchInScreen = HwNotchSizeUtil.getMethod("hasNotchInScreen"); if(hasNotchInScreen != null) { hasNotch = (boolean) hasNotchInScreen.invoke(HwNotchSizeUtil); } } catch (Exception e) { e.printStackTrace(); } return hasNotch; } /** * VIVO劉海屏判斷 * @return */ public static boolean hasNotchInVivo(Context context) { boolean hasNotch = false; try { ClassLoader cl = context.getClassLoader(); Class ftFeature = cl.loadClass("android.util.FtFeature"); Method[] methods = ftFeature.getDeclaredMethods(); if (methods != null) { for (int i = 0; i < methods.length; i++) { Method method = methods[i]; if(method != null) { if (method.getName().equalsIgnoreCase("isFeatureSupport")) { hasNotch = (boolean) method.invoke(ftFeature, 0x00000020); break; } } } } } catch (Exception e) { e.printStackTrace(); hasNotch = false; } return hasNotch; } /** * OPPO劉海屏判斷 * @return */ public static boolean hasNotchInOppo(Context context) { return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism"); } public static boolean isXiaomi() { return "Xiaomi".equals(Build.MANUFACTURER); }
使用程式碼:
if(judgeNotchUtils.hasNotchScreen(BaseActivity.this)){
// 有劉海屏的處理
// 顯示狀態列
// 狀態列文字、圖示顏色控制
} else {
// 無劉海屏的處理
// 隱藏狀態列
}
2)狀態列文字圖示顏色控制 工具類StatusBarUtils
public class StatusBarUtils { /** * 修改狀態列為全透明 * @param activity */ @TargetApi(19) public static void transparencyBar(Activity activity){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = activity.getWindow(); window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(Color.TRANSPARENT); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { Window window =activity.getWindow(); window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } } /** * 修改狀態列顏色,支援4.4以上版本 * @param activity * @param colorId */ public static void setStatusBarColor(Activity activity,int colorId) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = activity.getWindow(); // window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(activity.getResources().getColor(colorId)); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //使用SystemBarTint庫使4.4版本狀態列變色,需要先將狀態列設定為透明 transparencyBar(activity); SystemBarTintManager tintManager = new SystemBarTintManager(activity); tintManager.setStatusBarTintEnabled(true); tintManager.setStatusBarTintResource(colorId); } } /** *狀態列亮色模式,設定狀態列黑色文字、圖示, * 適配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android * @param activity * @return 1:MIUUI 2:Flyme 3:android6.0 */ public static int StatusBarLightMode(Activity activity){ int result=0; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if(MIUISetStatusBarLightMode(activity, true)){ result=1; }else if(FlymeSetStatusBarLightMode(activity.getWindow(), true)){ result=2; }else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { activity.getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); result=3; } } return result; } /** * 已知系統型別時,設定狀態列黑色文字、圖示。 * 適配4.4以上版本MIUIV、Flyme和6.0以上版本其他Android * @param activity * @param type 1:MIUUI 2:Flyme 3:android6.0 */ public static void StatusBarLightMode(Activity activity,int type){ if(type==1){ MIUISetStatusBarLightMode(activity, true); }else if(type==2){ FlymeSetStatusBarLightMode(activity.getWindow(), true); }else if(type==3){ activity.getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); } } /** * 狀態列暗色模式,清除MIUI、flyme或6.0以上版本狀態列黑色文字、圖示 */ public static void StatusBarDarkMode(Activity activity,int type){ if(type==1){ MIUISetStatusBarLightMode(activity, false); }else if(type==2){ FlymeSetStatusBarLightMode(activity.getWindow(), false); }else if(type==3){ activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); } } /** * 設定狀態列圖示為深色和魅族特定的文字風格 * 可以用來判斷是否為Flyme使用者 * @param window 需要設定的視窗 * @param dark 是否把狀態列文字及圖示顏色設定為深色 * @return boolean 成功執行返回true * */ public static boolean FlymeSetStatusBarLightMode(Window window, boolean dark) { boolean result = false; if (window != null) { try { WindowManager.LayoutParams lp = window.getAttributes(); Field darkFlag = WindowManager.LayoutParams.class .getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON"); Field meizuFlags = WindowManager.LayoutParams.class .getDeclaredField("meizuFlags"); darkFlag.setAccessible(true); meizuFlags.setAccessible(true); int bit = darkFlag.getInt(null); int value = meizuFlags.getInt(lp); if (dark) { value |= bit; } else { value &= ~bit; } meizuFlags.setInt(lp, value); window.setAttributes(lp); result = true; } catch (Exception e) { } } return result; } /** * 需要MIUIV6以上 * @param activity * @param dark 是否把狀態列文字及圖示顏色設定為深色 * @return boolean 成功執行返回true * */ public static boolean MIUISetStatusBarLightMode(Activity activity, boolean dark) { boolean result = false; Window window=activity.getWindow(); if (window != null) { Class clazz = window.getClass(); try { int darkModeFlag = 0; Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams"); Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE"); darkModeFlag = field.getInt(layoutParams); Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class); if(dark){ extraFlagField.invoke(window,darkModeFlag,darkModeFlag);//狀態列透明且黑色字型 }else{ extraFlagField.invoke(window, 0, darkModeFlag);//清除黑色字型 } result=true; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //開發版 7.7.13 及以後版本採用了系統API,舊方法無效但不會報錯,所以兩個方式都要加上 if(dark){ activity.getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); }else { activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); } } }catch (Exception e){ } } return result; } }
使用時程式碼如下: 比如博主的開發的應用是淺色背景色,所以標題欄也被設為淺色,此時應該修改狀態列顯色黑色文字圖示
if(judgeNotchUtils.hasNotchScreen(BaseActivity.this)){
// 有劉海屏的處理
// 顯示狀態啦
// 將狀態列文字圖示設為黑色
StatusBarUtils.StatusBarLightMode(BaseActivity.this)
} else {
// 無劉海屏的處理 隱藏狀態列
}
效果圖如下:
①狀態列文字、圖示設為黑色的應用頁:
②文字、圖示不改變顏色,仍為白色的狀態列:
以上圖片,讀者湊合看一下,意思到了就行,不好截劉海屏的小劉海,所以後期自己加了形狀表示一下。o(╯□╰)oo(╯□╰)o
③不適配劉海屏時的圖片:
附上各劉海屏手機廠商的劉海屏適配方案(謝謝奧特曼超人博主的分享):
更多劉海屏適配文章:
Android 劉海屏適配全攻略
android 全面屏/劉海屏有效適配
詳解Android劉海屏適配