Android 劉海屏適配

weixin_34239169發表於2019-01-28

什麼是劉海屏

螢幕的正上方居中位置(上圖黑色區域)會被挖掉一個孔,螢幕被挖掉的區域無法正常顯示內容,這種型別的螢幕就是劉海屏,也有其他叫法:挖孔屏、凹凸屏等等,為便於說明,後文提到的「劉海屏」都同時指代上圖兩種螢幕。Apple一直在引領設計的潮流,自從 iPhone X 釋出之後,”劉海屏” 就一直存在爭議,本以為是一個美麗的錯誤(Bug),卻造就了一時間“劉海屏”的模仿潮,由於Android在9.0之前沒有給出官方指導,所以出現了各個廠商自掃門前雪的狀況。本文主要講述Android 8.0在各個手機上的適配方案,包括小米、Oppo/ViVo、華為、三星等主流機型,並在文末提及Android P統一適配方案。

小米 MIUI Notch 屏適配說明

目前已上市的小米 Notch 裝置(俗稱劉海屏手機)如下,其寬度、高度和形狀均略有差異

機型

model

device

解析度

Notch高度

Notch寬度

DPI

小米8

MI 8

dipper

1080*2248

89

560

440

小米8 SE

MI 8 SE

sirius

1080*2244

85

540

440

小米8 透明探索版

MI 8 Explorer Edition

ursa

1080*2248

89

560

440

小米8 螢幕指紋版

MI 8 UD

equuleus

1080*2248

89

560

440

小米8 青春版

MI8Lite

platina

1080*2280

82

296

440

小米POCO F1

POCO F1

beryllium

1080*2246

86

588

440

紅米6 Pro

Redmi 6 Pro

sakura

1080*2280

89

352

440

注:以上裝置,由於MIUI調整了DPI值,因此DP值與畫素值的轉換關係是 1dp = 2.75 px 。

如何判斷裝置為 Notch 機型

系統增加了 property ro.miui.notch,值為1時則是 Notch 屏手機。

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

如何獲取 Notch / 凹口 / 劉海 的高度和寬度(截至2018.6.26)

MIUI 10 新增了獲取劉海寬和高的方法,需升級至8.6.26開發版及以上版本。

以下是獲取當前裝置劉海高度的方法:

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

以下是獲取當前裝置劉海寬度的方法:

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

注意上述獲取寬高的方法僅限於2018.6.26的以上的版本,那麼如何判斷小米的MIUI 的版本 ?

/**
 * 判斷是否大於 1529942400 ,2018.6.26 轉為毫秒是 1529942400 ,MIUI10 的版本Version codetime
 * @return
 */
public boolean isGreaterMIUI10() {
    return  (Long.parseLong(checkMIUI()) >= 1529942400);
}

public static String checkMIUI() {
    String versionCode = "";
    String manufacturer = Build.MANUFACTURER;
    String model = Build.MODEL;
    Logger.d(TAG,"Build.MANUFACTURER = " + manufacturer + " ,Build.MODEL = " + Build.MODEL);
    if (!TextUtils.isEmpty(manufacturer) && manufacturer.equals("Xiaomi")) {
        versionCode = getSystemProperty("ro.miui.version.code_time");
    }
    return versionCode;
}複製程式碼

2018.6.26的以下的版本參考1.3.

狀態列高度獲取方法

由於 Notch 裝置的狀態列高度與正常機器不一樣,因此在需要使用狀態列高度時,不建議寫死一個值,而應該改為讀取系統的值。

以下是獲取當前裝置狀態列高度的方法:

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

Oppo 凹形屏適配說明

如何判斷裝置為劉海屏 機型

本次凹形屏規格的機型型號:
PAAM00
PAAT00
PACM00
PACT00
CPH1831
CPH1833
上述機型的螢幕規格完全相同,不需分別做差異化處理,統一適配即可。

context.getPackageManager().hasSystemFeature(“com.oppo.feature.screen.heteromorphism”),返回 true為凹形屏 ,可識別OPPO的手機是否為凹形屏。

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

如何獲取 Notch / 凹口 / 劉海 的高度和寬度

OPPO不提供介面獲取劉海尺寸,目前其有劉海屏的機型尺寸規格都是統一的。

採用寬度為1080px, 高度為2280px的圓弧螢幕。 螢幕頂部凹形區域不能顯示內容,寬度為324px, 高度為80px。

圖左:全屏顯示示意圖,綠色區域為可顯示區域
圖右:16:9顯示示意圖,綠色區域為可顯示區域

VIVO 全面屏應用適配指南

如何判斷是劉海屏機型

publicstaticfinalint VIVO_NOTCH = 0x00000020;//是否有劉海publicstaticfinalint VIVO_FILLET = 0x00000008;//是否有圓角publicstaticbooleanhasNotchAtVivo(Context context){
        boolean ret = false;
        try {
            ClassLoader classLoader = context.getClassLoader();
            Class FtFeature = classLoader.loadClass("android.util.FtFeature");
            Method method = FtFeature.getMethod("isFeatureSupport", int.class);
            ret = (boolean) method.invoke(FtFeature, VIVO_NOTCH);
        } catch (ClassNotFoundException e) {
            Log.e("Notch", "hasNotchAtVivo ClassNotFoundException");
        } catch (NoSuchMethodException e) {
            Log.e("Notch", "hasNotchAtVivo NoSuchMethodException");
        } catch (Exception e) {
            Log.e("Notch", "hasNotchAtVivo Exception");
        } finally {
            return ret;
        }
    }複製程式碼

如何獲取 Notch / 凹口 / 劉海 的高度和寬度

vivo不提供介面獲取劉海尺寸,目前vivo的劉海寬為100dp,高為27dp。同樣建議使用2.2中狀態列高度的方式適配。

華為 劉海屏手機安卓O版本適配指導

華為的文件是比較標準的,更為貼近Android P官方提供的方案。

如何判斷是否有劉海屏

類檔案介面介面說明
com.huawei.android.util.HwNotchSizeUtilpublic static boolean hasNotchInScreen()

是否是劉海屏手機:
true:是劉海屏;false:非劉海屏。

publicstaticbooleanhasNotchAtHuawei(Context context){
        boolean ret = false;
        try {
            ClassLoader classLoader = context.getClassLoader();
            Class HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil");
            Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen");
            ret = (boolean) get.invoke(HwNotchSizeUtil);
        } catch (ClassNotFoundException e) {
            Log.e("Notch", "hasNotchAtHuawei ClassNotFoundException");
        } catch (NoSuchMethodException e) {
            Log.e("Notch", "hasNotchAtHuawei NoSuchMethodException");
        } catch (Exception e) {
            Log.e("Notch", "hasNotchAtHuawei Exception");
        } finally {
            return ret;
        }
    }複製程式碼

如何獲取 劉海 的高度和寬度

類檔案介面介面說明
com.huawei.android.util.HwNotchSizeUtilpublic static int[] getNotchSize()

獲取劉海尺寸:width、height

int[0]值為劉海寬度 int[1]值為劉海高度。

//獲取劉海尺寸:width、height//int[0]值為劉海寬度 int[1]值為劉海高度publicstaticint[] getNotchSizeAtHuawei(Context context) {
        int[] ret = newint[]{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("Notch", "getNotchSizeAtHuawei ClassNotFoundException");
        } catch (NoSuchMethodException e) {
            Log.e("Notch", "getNotchSizeAtHuawei NoSuchMethodException");
        } catch (Exception e) {
            Log.e("Notch", "getNotchSizeAtHuawei Exception");
        } finally {
            return ret;
        }
    }複製程式碼

三星

/**
 * 三星手機SM-G8870 劉海屏適配
 *
 */
@RequiresApi(api = Build.VERSION_CODES.O)
public void getSamsungCutOutInfo() {
    try {
        final View decorView = mActivity.getWindow().getDecorView();
        WindowInsets windowInsets = decorView.getRootWindowInsets();

        WindowInsetsWrapper wrapper = new WindowInsetsWrapper(windowInsets);
        DisplayCutoutWrapper mLastDisplayCutoutWrapper = wrapper.getDisplayCutoutWrapper();
        if (mLastDisplayCutoutWrapper == null) {
            isSamsungCutOutMode = false;
            return;
        }
        List<Rect> boundingRects = mLastDisplayCutoutWrapper.getBoundingRects();

        if (boundingRects == null || boundingRects.size() == 0) {
            Logger.d(TAG, "is not notch screen");
            isSamsungCutOutMode = false;
        } else {
            isSamsungCutOutMode = true;
            Logger.d(TAG, "Top=" + mLastDisplayCutoutWrapper.getSafeInsetTop());
            Logger.d(TAG, "Bottom=" + mLastDisplayCutoutWrapper.getSafeInsetBottom());
            Logger.d(TAG, "Left=" + mLastDisplayCutoutWrapper.getSafeInsetLeft());
            Logger.d(TAG, "Right=" + mLastDisplayCutoutWrapper.getSafeInsetRight());
            mSamsungSafeInsetHeight = mLastDisplayCutoutWrapper.getSafeInsetLeft() + mLastDisplayCutoutWrapper.getSafeInsetTop();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}複製程式碼

谷歌P版本劉海屏適配方案

特性介紹

谷歌稱劉海屏為凹口屏以及螢幕缺口支援, 下面的內容摘自:developer.android.com/preview/fea…

Android P 支援最新的全面屏以及為攝像頭和揚聲器預留空間的凹口螢幕。 通過全新的 DisplayCutout 類,可以確定非功能區域的位置和形狀,這些區域不應顯示內容。 要確定這些凹口螢幕區域是否存在及其位置,請使用 getDisplayCutout() 函式。

1. 全新的視窗布局屬性 layoutInDisplayCutoutMode 讓您的應用可以為裝置凹口螢幕周圍的內容進行佈局。 您可以將此屬性設為下列值之一:

(1)LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
(2)LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
(3)LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER

2. 您可以按如下方法在任何執行 Android P 的裝置或模擬器上模擬螢幕缺口:

(1)啟用開發者選項。
(2)在 Developer options 螢幕中,向下滾動至 Drawing 部分並選擇 Simulate a display with a cutout。
(3)選擇凹口螢幕的大小。

注意:

我們建議您通過使用執行 Android P 的裝置或模擬器測試凹口螢幕周圍的內容顯示。

介面介紹

1. 獲取劉海尺寸相關介面:

developer.android.com/reference/a…

所屬類方法介面說明
android.view.DisplayCutoutList<Rect> getBoundingRects()返回Rects的列表,每個Rects都是螢幕上非功能區域的邊界矩形。裝置的每個短邊最多隻有一個非功能區域,而長邊上則沒有。
android.view.DisplayCutoutint getSafeInsetBottom()返回安全區域距離螢幕底部的距離,單位是px。
android.view.DisplayCutoutint getSafeInsetLeft ()返回安全區域距離螢幕左邊的距離,單位是px。
android.view.DisplayCutoutint getSafeInsetRight ()返回安全區域距離螢幕右邊的距離,單位是px。
android.view.DisplayCutoutint getSafeInsetTop ()返回安全區域距離螢幕頂部的距離,單位是px。

2. 設定是否延伸到劉海區顯示介面:

developer.android.com/reference/a…

屬性屬性說明
android.view.WindowManager.LayoutParamsint layoutInDisplayCutoutMode

預設值:

LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT

其他可能取值:

LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES

LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER

developer.android.com/reference/a…

常量常量說明
android.view.WindowManager.LayoutParamsint LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT

只有當DisplayCutout完全包含在系統狀態列中時,才允許視窗延伸到DisplayCutout區域顯示。

android.view.WindowManager.LayoutParamsint LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER

該視窗決不允許與DisplayCutout區域重疊。

android.view.WindowManager.LayoutParamsint LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES該視窗始終允許延伸到螢幕短邊上的DisplayCutout區域。

參考實現程式碼

1. 設定使用劉海區顯示程式碼:

getSupportActionBar().hide();
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); 
//設定頁面全屏顯示
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.layoutInDisplayCutoutMode = 
       windowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; 
//設定頁面延伸到劉海區顯示
getWindow().setAttributes(lp);複製程式碼

注意:如果需要應用的佈局延伸到劉海區顯示,需要設定SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN。

屬性:

不使用SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 使用SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

佈局無法延伸到劉海顯示 佈局可以正真延伸到劉海區顯示

2. 獲取劉海屏安全顯示區域和劉海尺寸資訊:

contentView = getWindow().getDecorView().findViewById(android.R.id.content).getRootView();
contentView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
    @Override
    public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
        DisplayCutout cutout = windowInsets.getDisplayCutout();
        if (cutout == null) {
            Log.e(TAG, "cutout==null, is not notch screen");//通過cutout是否為null判斷是否劉海屏手機
        } else {
            List<Rect> rects = cutout.getBoundingRects();
            if (rects == null || rects.size() == 0) {
                Log.e(TAG, "rects==null || rects.size()==0, is not notch screen");
            } else {
                Log.e(TAG, "rect size:" + rects.size());//注意:劉海的數量可以是多個
                for (Rect rect : rects) {
                    Log.e(TAG, "cutout.getSafeInsetTop():" + cutout.getSafeInsetTop()
                            + ", cutout.getSafeInsetBottom():" + cutout.getSafeInsetBottom()
                            + ", cutout.getSafeInsetLeft():" + cutout.getSafeInsetLeft()
                            + ", cutout.getSafeInsetRight():" + cutout.getSafeInsetRight()
                            + ", cutout.rects:" + rect
                    );
                }
            }
        }
        return windowInsets;
    }
});複製程式碼

3. 說明:

(1)通過windowInsets.getDisplayCutout()是否為null判斷是否劉海屏手機,如果為null為非劉海屏:

Line 6203: 05-24 11:16:46.766 11036 11036 E Cutout_test: cutout==null, is not notch screen複製程式碼

(2)如果是劉海屏手機可以通過介面獲取劉海資訊:

Line 6211: 05-24 11:11:16.839 10733 10733 E Cutout_test: cutout.getSafeInsetTop():126, cutout.getSafeInsetBottom():0, 
cutout.getSafeInsetLeft():0, cutout.getSafeInsetRight():0, cutout.rects:Rect(414, 0 - 666, 126)複製程式碼

(3)劉海個數可以是多個:

Line 6291: 05-24 11:27:04.517 11036 11036 E Cutout_test: rect size:2
Line 6292: 05-24 11:27:04.517 11036 11036 E Cutout_test: cutout.getSafeInsetTop():84, 
cutout.getSafeInsetBottom():84, cutout.getSafeInsetLeft():0, cutout.getSafeInsetRight():0, cutout.rects:Rect(351, 0 - 729, 84)
Line 6293: 05-24 11:27:04.517 11036 11036 E Cutout_test: cutout.getSafeInsetTop():84, 
cutout.getSafeInsetBottom():84, cutout.getSafeInsetLeft():0, cutout.getSafeInsetRight():0, cutout.rects:Rect(351, 1836 - 729, 1920)複製程式碼

參考文件

1、小米MIUI Notch 屏適配說明

2、Oppo 凹形屏適配說明

3、VIVO 全面屏應用適配指南

4、華為 劉海屏手機安卓O版本適配指導

5、Google官網 developer.android.com/preview/fea…

6、Android 劉海屏適配全攻略

碼字不易,如有建議請掃碼


相關文章