隨着 Apple 發佈 iPhone X 以後,各大手機廠商也開始模仿這種劉海屏的設計,並且劉海屏手機的用戶也是愈來愈大,前段時間將項目進行了全部主流廠商的劉海屏手機的適配,以便讓劉海屏手機的用戶也能有更好的體驗。html
劉海屏手機由於比日常的手機多了一塊頂部的遮擋性劉海,因此會形成頂部 Toolbar 以及搜索框的遮擋,並且有些廠商的手機(vivo、華爲),默認是在「無狀態欄」的界面將狀態欄進行黑化顯示,這時候會致使系統下移,從而致使底部的一些 UI 被截斷。除此以外,一些控件的顯示規則還會受到影響,如 PopupWindow 的顯示高度會在「無狀態欄」的界面中比普通手機低一個「劉海的高度」,從而遮擋住原先在 PopupWindow 周圍的圖標。android
理論上來說,經過 Android P 版本提供的劉海屏相關接口,判斷手機是否爲劉海屏手機,以及進行一些相應的處理是最合適的方式,但如今在國內使用 Android P 的接口是不現實的,因此只能經過各大廠商提供的技術文檔來進行適配,但適配的流程基本是一致的。c#
其中須要着重處理的是:api
如今國內的主流機型(華爲、vivo、OPPO、小米)在劉海屏的顯示上分爲兩個陣營:bash
因此,咱們在進行劉海屏適配的時候,首先須要經過一些手段,統一各大廠商的顯示方案,讓全部的劉海屏手機都利用狀態欄的界面,「告知系統」咱們已經適配了劉海屏,確保系統不會下移咱們的應用,保留原生體驗。markdown
這裏主要有兩種方式:oop
一、設置屏幕高寬比例spa
由於劉海屏手機的「寬高比」比以前的手機大,若是不適配的話,Android 默認爲最大的寬高比爲 1.86, 小於劉海屏手機的寬高比,所以咱們須要申明更高的寬高比來告訴系統,咱們應用已經適配了劉海屏。設計
只要在 AndroidManifest.xml 中加入以下配置:code
<meta-data android:name="android.max_aspect" android:value="2.1"/> 複製代碼
也能夠在 Application 添加屬性:
android:maxAspectRatio="ratio_float" 複製代碼
ps:這個屬性須要 API 26 才支持
二、設置應用支持 resize
咱們還能夠經過設置應用支持 resizeable,來告訴系統咱們適配了劉海屏,並且這也是 Google 官方推薦的方式。不過須要注意的是,使用這個屬性以後,應用也會跟着支持分屏模式。只須要在 AndroidManifest.xml 中添加:
android:resizeableActivity="true" 複製代碼
對於劉海屏適配,咱們將界面分爲兩種:
所以,咱們進行劉海屏適配,其實針對的就是沒有狀態欄的界面,而有狀態欄的界面顯示是正常的。對於沒有狀態欄的界面,主要是將對被劉海遮擋到的控件,設置對應劉海高度的 MarginTop,從而避免控件被遮擋。而對於底部可能被截斷的界面,能夠考慮將底部作成 ScrollView 的形式。
如今 Android P 的接口還無法用,但各手機廠商都制定了本身的 API,對此咱們須要對各大機型進行特殊的適配,這裏主要介紹 vivo、OPPO、華爲 這三種主流手機的適配方案。
華爲做爲國內的手機廠商大頭,本身仿照 Android P 提供的 API,實現了一套幾乎差很少的 API,因此咱們若是想要告訴系統咱們的應用適配了劉海屏,最好直接使用華爲的 API,這樣纔是最保險的。
如下代碼來自:華爲劉海屏適配官方技術指導
① 方案一:在 AndroidManifest.xml 中增長 meta-data 屬性,此屬性不只能夠針對 Application 生效,也能夠對 Activity 配置生效:
<meta-data android:name="android.notch_support" android:value="true"/> 複製代碼
增長這個屬性以後,系統就不會對應用進行下移處理,從而保證原生體驗。
② 方案二:經過添加窗口 FLAG 的方式設置界面使用劉海區:
public static void setFullScreenWindowLayoutInDisplayCutout(Window window) { if (window == null) { return; } WindowManager.LayoutParams layoutParams = window.getAttributes(); try { Class layoutParamsExCls = Class.forName("com.huawei.android.view.LayoutParamsEx"); Constructor con = layoutParamsExCls.getConstructor(LayoutParams.class); Object layoutParamsExObj = con.newInstance(layoutParams); Method method = layoutParamsExCls.getMethod("addHwFlags", int.class); method.invoke(layoutParamsExObj, FLAG_NOTCH_SUPPORT); } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException |InstantiationException | InvocationTargetException e) { Log.e("test", "hw add notch screen flag api error"); } catch (Exception e) { Log.e("test", "other Exception"); } } 複製代碼
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; } 複製代碼
public static int[] getNotchSize(Context context) { int[] ret = new int[]{0, 0}; try { ClassLoader cl = context.getClassLoader(); Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil"); Method get = HwNotchSizeUtil.getMethod("getNotchSize"); ret = (int[]) get.invoke(HwNotchSizeUtil); } catch (ClassNotFoundException e) { Log.e("test", "getNotchSize ClassNotFoundException"); } catch (NoSuchMethodException e) { Log.e("test", "getNotchSize NoSuchMethodException"); } catch (Exception e) { Log.e("test", "getNotchSize Exception"); } finally { return ret; } 複製代碼
OPPO 是主流廠商中的一股清流,學 iPhoneX 是最像的,OPPO 手機對於不顯示狀態欄的界面,採起的是「狀態欄原先的位置也用於顯示界面」的方案,因此咱們只要進行相關控件的位置移動就能夠了。
如下代碼來自: OPPO 凹形屏適配說明
public static boolean hasNotchInOppo(Context context) { return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism"); } 複製代碼
對於 OPPO 劉海屏手機的劉海高度,OPPO 官方的文檔沒有提供相關的 API,但官方文檔表示 OPPO 手機的劉海高度和狀態欄的高度是一致的,並且我也對此進行了驗證,確實如此。因此咱們能夠直接獲取狀態欄的高度,做爲 OPPO 手機的劉海高度。
public static int getStatusBarHeight(Context context) { int statusBarHeight = 0; int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { statusBarHeight = context.getResources().getDimensionPixelSize(resourceId); } return statusBarHeight ; } 複製代碼
vivo 提供的技術文檔對於開發者來講是最不友好的,只提供了一個 API 來進行劉海屏的判斷,並無提供劉海高度的獲取方式,咱們只能經過獲取狀態欄高度來當作劉海的高度,但在某些機型可能會有些誤差。
官方文檔:vivo 手機適配指南
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; } 複製代碼
以上即是在以前在進行 Android 劉海屏適配的時候,所積累的一些經驗和心得。將其記錄下來,以便本身之後進行回顧,同時也但願這篇文章能對進行劉海屏適配的同窗一些幫助。