Android P 劉海屏適配全攻略

本文由 玉剛說寫做平臺 提供寫做贊助java

原做者:四月葡萄android

版權聲明:本文版權歸微信公衆號 玉剛說 全部,未經許可,不得以任何形式轉載安全

1.前言

先吐槽一下,劉海屏真醜。然而做爲苦逼的開發者,仍是要去適配劉海屏的。好了,吐槽完畢,進入正題。 微信

劉海啦啦.jpeg
這裏主要是介紹一下Android P中劉海屏的適配以及Android P以前的適配。爲何要分開呢?由於Android P以前官方還沒提供API來進行適配,都是由各家廠商來提供適配方案的。

2.Android P中的劉海屏適配

2.1 Google對劉海屏的支持介紹

Google將劉海屏命名爲屏幕缺口了,這一小節內容摘自Android官方介紹: 屏幕缺口支持ide

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

全新的窗口布局屬性 layoutInDisplayCutoutMode 讓您的應用能夠爲設備凹口屏幕周圍的內容進行佈局。 您能夠將此屬性設爲下列值之一:佈局

  • LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
  • LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
  • LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER

您能夠按以下方法在任何運行 Android P 的設備或模擬器上模擬屏幕缺口:post

  1. 啓用開發者選項。
  2. Developer options(開發者選項) 屏幕中,向下滾動至 Drawing(繪圖) 部分並選擇 Simulate a display with a cutout(模擬具備凹口的顯示屏)
  3. 選擇凹口屏幕的大小。

經過使用模擬器測試凹口屏幕.png
注:咱們建議您經過使用運行 Android P 的設備或模擬器測試凹口屏幕周圍的內容顯示。

2.2 Android P提供提供的劉海屏適配方案

  1. 對於有狀態欄的頁面,不會受到劉海屏特性的影響,由於劉海屏包含在狀態欄中了;
  2. 全屏顯示的頁面,系統劉海屏方案會對應用界面作下移處理,避開劉海區顯示,這時會看到劉海區域變成一條黑邊,徹底看不到劉海了;
  3. 已經適配Android P應用的全屏頁面能夠經過谷歌提供的適配方案使用劉海區,真正作到全屏顯示。

2.3 Android P中支持的凹口屏幕類型

目前Android支持了三類凹口屏幕類型:邊角顯示屏凹口(斜劉海)雙顯示屏凹口(劉海+鬍子)長型顯示屏凹口(劉海),以下圖所示: 測試

邊角顯示屏凹口(斜劉海).png

雙顯示屏凹口(劉海+鬍子).png

長型顯示屏凹口(劉海).png

目前的手機主要仍是長型顯示屏凹口,即劉海屏。其餘斜劉海和鬍子手機應該尚未實物吧?反正是亮瞎了狗眼了。ui

2.4 劉海屏佈局及安全區域說明

佈局區域.png

2.5 Android P中凹口屏幕相關接口

注意,如下接口都是要Build.VERSION.SDK_INT >= 28才能調用到。

2.5.1 DisplayCutout類接口

主要用於獲取凹口位置和安全區域的位置等。主要接口以下所示:

方法 接口說明
getBoundingRects() 返回Rects的列表,每一個Rects都是顯示屏上非功能區域的邊界矩形。
getSafeInsetLeft () 返回安全區域距離屏幕左邊的距離,單位是px。
getSafeInsetRight () 返回安全區域距離屏幕右邊的距離,單位是px。
getSafeInsetTop () 返回安全區域距離屏幕頂部的距離,單位是px。
getSafeInsetBottom() 返回安全區域距離屏幕底部的距離,單位是px。

來看下例子。 這裏將開發者選項中的模擬具備凹口的顯示屏選項改成雙顯示屏凹口,即這裏應當有兩個劉海,而後,直接上代碼:

public class NotchActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //開局就一張背景圖
        setContentView(R.layout.notch);

        getNotchParams();
    }

    @TargetApi(28)
    public void getNotchParams() {
        final View decorView = getWindow().getDecorView();

        decorView.post(new Runnable() {
            @Override
            public void run() {
                DisplayCutout displayCutout = decorView.getRootWindowInsets().getDisplayCutout();
                Log.e("TAG", "安全區域距離屏幕左邊的距離 SafeInsetLeft:" + displayCutout.getSafeInsetLeft());
                Log.e("TAG", "安全區域距離屏幕右部的距離 SafeInsetRight:" + displayCutout.getSafeInsetRight());
                Log.e("TAG", "安全區域距離屏幕頂部的距離 SafeInsetTop:" + displayCutout.getSafeInsetTop());
                Log.e("TAG", "安全區域距離屏幕底部的距離 SafeInsetBottom:" + displayCutout.getSafeInsetBottom());
                
                List<Rect> rects = displayCutout.getBoundingRects();
                if (rects == null || rects.size() == 0) {
                    Log.e("TAG", "不是劉海屏");
                } else {
                    Log.e("TAG", "劉海屏數量:" + rects.size());
                    for (Rect rect : rects) {
                        Log.e("TAG", "劉海屏區域:" + rect);
                    }
                }
            }
        });
    }
}
複製代碼

輸出結果爲:

06-04 21:57:10.120 5698-5698/? E/TAG: 安全區域距離屏幕左邊的距離 SafeInsetLeft:0
06-04 21:57:10.120 5698-5698/? E/TAG: 安全區域距離屏幕右部的距離 SafeInsetRight:0
06-04 21:57:10.120 5698-5698/? E/TAG: 安全區域距離屏幕頂部的距離 SafeInsetTop:112
06-04 21:57:10.120 5698-5698/? E/TAG: 安全區域距離屏幕底部的距離 SafeInsetBottom:112
06-04 21:57:10.120 5698-5698/? E/TAG: 劉海屏數量:2
06-04 21:57:10.120 5698-5698/? E/TAG: 劉海屏區域:Rect(468, 0 - 972, 112)
06-04 21:57:10.120 5698-5698/? E/TAG: 劉海屏區域:Rect(468, 2448 - 972, 2560)
複製代碼

能夠看到,即距離頂部和底部各112px的區域就是安全區域了。

2.5.2 設置凹口屏幕顯示模式

使用例子:

WindowManager.LayoutParams lp = getWindow().getAttributes();
    lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
    getWindow().setAttributes(lp);
複製代碼

Android P中新增了一個佈局參數屬性layoutInDisplayCutoutMode,包含了三種不一樣的模式,以下所示:

模式 模式說明
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 只有當DisplayCutout徹底包含在系統欄中時,才容許窗口延伸到DisplayCutout區域。 不然,窗口布局不與DisplayCutout區域重疊。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 該窗口決不容許與DisplayCutout區域重疊。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 該窗口始終容許延伸到屏幕短邊上的DisplayCutout區域。

下面咱們來寫個Demo看下這三種模式的顯示效果: Demo很簡單,就是顯示一張背景圖,相關背景佈局就不貼了,來看下主要的代碼:

public class NotchActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //去掉標題
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        //開局就一張背景圖
        setContentView(R.layout.notch);

        //全屏顯示
        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);

        WindowManager.LayoutParams lp = getWindow().getAttributes();
        
        //下面圖1
        lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
        //下面圖2
// lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
        //下面圖3
// lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
        getWindow().setAttributes(lp);
    }
}
複製代碼

這裏設置爲全屏的顯示效果,三種模式的結果以下圖所示:

LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER.png
圖一能夠看到上面有黑邊。

LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES.png
圖二明顯看到有劉海。

LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT.png
圖三一樣是黑邊。

能夠看到:

  1. LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES模式會讓屏幕到延申劉海區域中。
  2. LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER模式不會讓屏幕到延申劉海區域中,會留出一片黑色區域。
  3. LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT模式在全屏顯示下跟LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER同樣。

咱們再來看看LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT模式在沉浸式狀態欄下的效果,代碼以下:

public class NotchActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //去掉標題
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        //開局就一張背景圖
        setContentView(R.layout.notch);

        //全屏顯示
// getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);

        //沉浸式狀態欄
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);

        WindowManager.LayoutParams lp = getWindow().getAttributes();

        lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;

        getWindow().setAttributes(lp);

    }
}
複製代碼

以下圖所示:

LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT.png

能夠看到:

當劉海區域徹底在系統的狀態欄時,LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT的顯示效果與LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES一致。

因此,當咱們進行劉海屏的適配時,請根據實際狀況去使用不一樣的layoutInDisplayCutoutMode

2.6 那麼劉海屏該如何適配呢?

2.6.1 若是頁面存在狀態欄

  • 那麼很簡單,不用適配,由於劉海區域會包含在狀態欄中了。
  • 若是不想看到劉海區域,可使用LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER將劉海區域變成一條黑色邊。

2.6.2 若是頁面是全屏顯示

  • 不適配的話將會留出一條黑色邊。
  • 要作到真正全屏的話,那麼就先要獲取到劉海的區域(危險區域),內容部分(操做按鈕等)應當避開危險區域,保證在安全區域中展現。橫屏的話兩邊都須要注意避開劉海(危險區域)。

3.Android P以前的劉海屏適配

上面是Android P纔有的解決方案,在P以前呢,上面的代碼統統都沒用。然而咱們偉大的國產廠商在Android P以前(基本都是Android O)就用上了高檔大氣上檔次的劉海屏,因此,這也造就了各大廠商在Android P以前的解決方案百花齊放。下面,咱們來看下主流廠商:華爲、vivo、OPPO、小米等所提供的方案。

注:相關的代碼都已封裝好,能夠直接拷貝使用。

3.1 華爲

3.1.1 使用劉海區顯示

使用新增的meta-data屬性android.notch_support。 在應用的AndroidManifest.xml中增長meta-data屬性,此屬性不只能夠針對Application生效,也能夠對Activity配置生效。 以下所示:

<meta-data android:name="android.notch_support" android:value="true"/>
複製代碼
  • Application生效,意味着該應用的全部頁面,系統都不會作豎屏場景的特殊下移或者是橫屏場景的右移特殊處理。
  • Activity生效,意味着能夠針對單個頁面進行劉海屏適配,設置了該屬性的Activity系統將不會作特殊處理。

實際上還有一種代碼實現的方式,不過代碼比較多,這裏就不貼了,有興趣的話能夠在文末的連接中點進去看看。

3.1.2 是否有劉海屏

經過如下代碼便可知道華爲手機上是否有劉海屏了,true爲有劉海,false則沒有。

public static boolean hasNotchAtHuawei(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;
        }
    }
複製代碼

3.1.3 劉海尺寸

華爲提供了接口獲取劉海的尺寸,以下:

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

3.2 vivo

vivo在設置--顯示與亮度--第三方應用顯示比例中能夠切換是否全屏顯示仍是安全區域顯示。

3.2.1 是否有劉海屏

public static final int VIVO_NOTCH = 0x00000020;//是否有劉海
    public static final int VIVO_FILLET = 0x00000008;//是否有圓角

    public static boolean hasNotchAtVoio(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", "hasNotchAtVoio ClassNotFoundException");
        } catch (NoSuchMethodException e) {
            Log.e("Notch", "hasNotchAtVoio NoSuchMethodException");
        } catch (Exception e) {
            Log.e("Notch", "hasNotchAtVoio Exception");
        } finally {
            return ret;
        }
    }
複製代碼

3.2.2 劉海尺寸

vivo不提供接口獲取劉海尺寸,目前vivo的劉海寬爲100dp,高爲27dp。

vivo機型.png

3.3 OPPO

OPPO目前在設置 -- 顯示 -- 應用全屏顯示 -- 凹形區域顯示控制,裏面有關閉凹形區域開關。

3.3.1 是否有劉海屏

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

3.3.2 劉海尺寸

OPPO不提供接口獲取劉海尺寸,目前其有劉海屏的機型尺寸規格都是統一的。不排除之後機型會有變化。 其顯示屏寬度爲1080px,高度爲2280px。劉海區域則都是寬度爲324px, 高度爲80px。

oppo機型.png

3.4 小米

3.4.1 是否有劉海屏

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

手頭上沒有小米8的手機,暫時無法驗證,這裏就不貼代碼了,省得誤導你們。後面測試過再放出來。

3.4.2 劉海尺寸

小米的狀態欄高度會略高於劉海屏的高度,所以能夠經過獲取狀態欄的高度來間接避開劉海屏,獲取狀態欄的高度代碼以下:

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

其餘手機也能夠經過這個方法來間接避開劉海屏,可是有可能有些手機的劉海屏高度會高於狀態欄的高度,因此這個方法獲取到的結果並不必定安全。

3.5 其餘廠商

若是要適配其餘廠商的劉海屏,能夠去找下他們的開發者文檔,通常都會有提供的,這裏就不詳述了。

4 參考資料:

1.Android P 功能和 API

2.華爲劉海屏手機安卓O版本適配指導

3.vivo全面屏應用適配指南

4.OPPO凹形屏適配說明

5.MIUI Notch 屏適配說明

歡迎關注微信公衆號
相關文章
相關標籤/搜索