Android O, P 劉海屏及全面屏適配

JavaDog發表於2019-01-30

1. 背景

全面屏手機已成為當下的主流趨勢,高佔比的螢幕能夠為使用者帶來極致的體驗,但是全面屏手機給使用者帶來極致體驗的同時也給App的適配帶來很大的挑戰,未適配全面屏的App會遇到各種顯示問題。劉海屏是非全面屏到全面屏轉變過程中,整個產業給出的一種解決方案,劉海區是為了前置電子元器件(如前置攝像頭、揚聲器、距離感應器等)而不得不做出的技術妥協。iOS系統從iOS11開始支援劉海屏,而Android系統直到Android P才提供了劉海屏的官方支援,國內Top廠商(華為,小米,OPPO,VIVO)為了儘早推出自己的高屏佔比手機也紛紛在Android O開始提供了自己的定製化劉海區實現方案,進一步提高了適配的工作量。目前主流的劉海區方案如下圖所示:

Android O, P 劉海屏及全面屏適配

2. 沒有適配劉海屏會出現什麼情況?

  1. 內容區下移,頭部出現黑條 2. 頂部被劉海區遮擋

Android O, P 劉海屏及全面屏適配 Android O, P 劉海屏及全面屏適配

3. UI適配方案

我們先看一下Google官方的劉海規格圖:

l12.png

根據上圖可知劉海屏適配的關鍵點就是讓App的重要內容避開危險區域,具體就是中間黑色的凹槽區域因此我們適配可分為以下三步:

  1. 首先判斷是否為劉海屏手機
  2. 判斷佈局是否延伸到劉海區域
  3. 調整佈局保證重要內容避開劉海區域

3. Top廠商Android O劉海屏介面

1. 華為

判斷是否劉海屏介面:

public static boolean hasNotchInScreen(Context context) {
    boolean ret = false;
    try {
        ClassLoader cl = context.getClassLoader();
        Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
        Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen");
        ret = (boolean) get.invoke(HwNotchSizeUtil);
    } catch (Exception e) {
    } finally {
        return ret;
    }
}複製程式碼

獲取劉海尺寸介面:

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 (Exception e) {
    } finally {
        return ret;
    }
}複製程式碼

2. 小米

判斷是否劉海屏介面:

SystemProperties.getInt("ro.miui.notch", 0) == 1;複製程式碼

獲取劉海尺寸介面:

int resourceId = context.getResources().getIdentifier("notch_height", "dimen", "android");
if (resourceId > 0) {
    result = context.getResources().getDimensionPixelSize(resourceId);
}複製程式碼

3. OPPO

判斷是否劉海屏介面:

context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");複製程式碼

獲取劉海尺寸介面(由於OPPO沒有提供獲取劉海屏尺寸的介面所以我們直接獲取狀態高度就可以了,因為劉海區域肯定是小於等於狀態列高度的):

public static int getNotchHeight(Context context) {
    int statusBarHeight = 0;
    try {
        int resourceId = context.getResources().getIdentifier(
                "status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            statusBarHeight = context.getResources()
                    .getDimensionPixelSize(resourceId);
        }
    } catch (Exception e) {
    }
    return statusBarHeight;
}複製程式碼

4. VIVO

判斷是否劉海屏介面:

public boolean hasNotchInScreen(Context context) {
        booblen hasNotch = false;
        try {
            ClassLoader cl = context.getClassLoader();
            @SuppressLint("PrivateApi") Class ftFeature = cl.loadClass("android.util.FtFeature");
            Method[] methods = ftFeature.getDeclaredMethods();
            for (Method method : methods) {
                if (method.getName()
                        .equalsIgnoreCase("isFeatureSupport")) {
                    hasNotch = (boolean) method
                            .invoke(ftFeature, NOTCH_IN_SCREEN_VOIO);
                    break;
                }
            }
        } catch (Exception ignored) {
        }
    }
    return hasNotch;
}複製程式碼

獲取劉海尺寸介面(由於VIVO沒有提供獲取劉海屏尺寸的介面所以我們直接獲取狀態高度就可以了,因為劉海區域肯定是小於等於狀態列高度的):

public static int getNotchHeight(Context context) {
    int statusBarHeight = 0;
    try {
        int resourceId = context.getResources().getIdentifier(
                "status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            statusBarHeight = context.getResources()
                    .getDimensionPixelSize(resourceId);
        }
    } catch (Exception e) {
    }
    return statusBarHeight;
}複製程式碼

4. Android P劉海屏適配方案

判斷是否劉海屏介面,需要注意的是,這裡的attachedView不能在onCreate的時候隨便傳一個view,那時候view還沒有Attach到window,拿到的RootWindowInsets是null:

public boolean hasNotchInScreen(View attachedView) {
    boolean hasNotch = false;
	WindowInsets windowInsets = attachedView.getRootWindowInsets();
	if (windowInsets != null) {
	    DisplayCutout displayCutout = windowInsets.getDisplayCutout();
	    if (displayCutout != null) {
	        List<Rect> rects = displayCutout.getBoundingRects();
	        if (rects != null && rects.size() > 0) {
	            hasNotch = true;
	        }
	    }
	}
	return hasNotch;
}複製程式碼

獲取劉海尺寸介面:

DisplayCutout類中就已經包含了劉海區域資訊,所以在在hasNotchInScreen中就可以儲存下來後面使用

讓頁面延伸到劉海區域WindowManager.LayoutParams.layoutInDisplayCutoutMode:

// DEFAULT LayoutFullScrren可以侵入,其餘不會侵入劉海區域
public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT = 0;
// 這個狀態是永遠不會在劉海上繪製
public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER = 2;
// 使用這個狀態可以讓LayoutFullScreen和FullScrren的時候在劉海區域繪製
public static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES = 1;複製程式碼
啟動頁黑條:
啟動頁設定了預設背景,並且設定了Fullscreen,那麼在啟動的時候劉海會是一個大黑條,解決的方法是在Launcher Activity的theme中加入<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>

5. 全面屏適配

方案1:
AndroidManifest.xml 檔案新增屬性: <meta-data android:name="android.max_aspect" android:value="2.4" />
應用適配建議採用meta-data的方式,具體可以參考:developer.android.com/guide/pract…

方案2:
新增 android:resizeableActivity =“true”
此設定只針對Activity生效,且增加了此屬性該activity也會支援分屏顯示。

方案3:
修改AndroidManifest.xml檔案,設定targetSdkVersion>=26,就是應用升級到O版本,不需要設定其他任何屬性,預設在任何縱橫比的螢幕都能全屏顯示。(備註:有一種例外情況需要注意,應用如果已經適配到O版本,並且通過meta-data屬性android.max_aspect或者是android:MaxAspectRatio屬性設定了頁面支援的最大縱橫比,同時又通過android:resizeableActivity=“false”設定了頁面不支援分屏,這個時候系統會按照應用自己設定的最大縱橫比決定該頁面是否能全屏顯示,如果應用設定的最大縱橫比比手機螢幕比例小,那應用還是無法全屏顯示。)

碼字不易,如有建議請掃碼Android O, P 劉海屏及全面屏適配


相關文章