Android 劉海屏適配總結

developerHaoz發表於2018-10-21

一、簡介

隨著 Apple 釋出 iPhone X 之後,各大手機廠商也開始模仿這種劉海屏的設計,而且劉海屏手機的使用者也是越來越大,前段時間將專案進行了所有主流廠商的劉海屏手機的適配,以便讓劉海屏手機的使用者也能有更好的體驗。

二、劉海屏造成的 UI 顯示問題

劉海屏手機因為比平常的手機多了一塊頂部的遮擋性劉海,所以會造成頂部 Toolbar 以及搜尋框的遮擋,而且有些廠商的手機(vivo、華為),預設是在「無狀態列」的介面將狀態列進行黑化顯示,這時候會導致系統下移,從而導致底部的一些 UI 被截斷。除此之外,一些控制元件的顯示規則還會受到影響,如 PopupWindow 的顯示高度會在「無狀態列」的介面中比普通手機低一個「劉海的高度」,從而遮擋住原先在 PopupWindow 周圍的圖示。

1、系統下移造成的底部 UI 截斷

小說頁碼被截斷

2、劉海擋住標題欄和搜尋框

劉海擋住標題欄和搜尋框

3、PopupWindow 顯示異常

PopupWindow 顯示異常

三、通用的適配方案

理論上來講,通過 Android P 版本提供的劉海屏相關介面,判斷手機是否為劉海屏手機,以及進行一些相應的處理是最合適的方式,但現在在國內使用 Android P 的介面是不現實的,所以只能通過各大廠商提供的技術文件來進行適配,但適配的流程基本是一致的。

劉海屏的適配流程

劉海屏的適配流程

其中需要著重處理的是:

  • 1、應用是否已經適配劉海屏
  • 2、頁面是否顯示狀態列

3.1 應用是否已經適配劉海屏

現在國內的主流機型(華為、vivo、OPPO、小米)在劉海屏的顯示上分為兩個陣營:

  • 當不顯示狀態列時,直接將介面進行顯示,「狀態列原先的位置也用於顯示介面」,例如:OPPO
  • 當不顯示狀態列時,直接「將狀態列原先的位置進行黑化,介面整體下移」,例如:華為、vivo

所以,我們在進行劉海屏適配的時候,首先需要通過一些手段,統一各大廠商的顯示方案,讓所有的劉海屏手機都利用狀態列的介面,「告知系統」我們已經適配了劉海屏,確保系統不會下移我們的應用,保留原生體驗。

這裡主要有兩種方式:

1、設定螢幕高寬比例

因為劉海屏手機的「寬高比」比之前的手機大,如果不適配的話,Android 預設為最大的寬高比為 1.86, 小於劉海屏手機的寬高比,因此我們需要申明更高的寬高比來告訴系統,我們應用已經適配了劉海屏。

只要在 AndroidManifest.xml 中加入如下配置:

<meta-data android:name="android.max_aspect"  android:value="2.1"/>
複製程式碼

也可以在 Application 新增屬性:

android:maxAspectRatio="ratio_float"
複製程式碼

ps:這個屬性需要 API 26 才支援

2、設定應用支援 resize

我們還可以通過設定應用支援 resizeable,來告訴系統我們適配了劉海屏,而且這也是 Google 官方推薦的方式。不過需要注意的是,使用這個屬性之後,應用也會跟著支援分屏模式。只需要在 AndroidManifest.xml 中新增:

android:resizeableActivity="true"
複製程式碼

3.2 頁面是否顯示狀態列

對於劉海屏適配,我們將介面分為兩種:

  • 對於有狀態列的介面,不會受到劉海屏的影響
  • 全屏顯示的介面(無狀態列),需要根據介面的顯示進行一些控制元件的下移

因此,我們進行劉海屏適配,其實針對的就是沒有狀態列的介面,而有狀態列的介面顯示是正常的。對於沒有狀態列的介面,主要是將對被劉海遮擋到的控制元件,設定對應劉海高度的 MarginTop,從而避免控制元件被遮擋。而對於底部可能被截斷的介面,可以考慮將底部做成 ScrollView 的形式。

四、各廠商的適配方案

現在 Android P 的介面還沒法用,但各手機廠商都制定了自己的 API,對此我們需要對各大機型進行特殊的適配,這裡主要介紹 vivo、OPPO、華為 這三種主流手機的適配方案。

華為

華為作為國內的手機廠商大頭,自己仿照 Android P 提供的 API,實現了一套幾乎差不多的 API,所以我們如果想要告訴系統我們的應用適配了劉海屏,最好直接使用華為的 API,這樣才是最保險的。

以下程式碼來自:華為劉海屏適配官方技術指導

1、應用頁面設定使用劉海區顯示

① 方案一:在 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");
     }
 }
複製程式碼

2、判斷該華為手機是否劉海屏

    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;
    }
複製程式碼

3、獲取劉海的高度

     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

OPPO 是主流廠商中的一股清流,學 iPhoneX 是最像的,OPPO 手機對於不顯示狀態列的介面,採取的是「狀態列原先的位置也用於顯示介面」的方案,所以我們只要進行相關控制元件的位置移動就可以了。

以下程式碼來自: OPPO 凹形屏適配說明

1、判斷該 OPPO 手機是否為劉海屏手機

    public static boolean hasNotchInOppo(Context context) {
        return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
    }
複製程式碼

2、獲取劉海屏的高度

對於 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

vivo 提供的技術文件對於開發者來說是最不友好的,只提供了一個 API 來進行劉海屏的判斷,並沒有提供劉海高度的獲取方式,我們只能通過獲取狀態列高度來當做劉海的高度,但在某些機型可能會有些偏差。

官方文件:vivo 手機適配指南

判斷該 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 劉海屏適配的時候,所積累的一些經驗和心得。將其記錄下來,以便自己以後進行回顧,同時也希望這篇文章能對進行劉海屏適配的同學一些幫助。

相關文章