歡迎光臨
每天分享高質量文章

Android 劉海屏適配方案總結

前言

目前市面上的劉海屏和水滴屏手機越來越多了,顏值方面是因人而異,有的人覺得很好看,也有人覺得醜爆了,我個人覺得是還可以。但是作為移動開發者來說,這並不是一件好事,越來越多異形屏手機的出現意味著我們需要投入大量精力在適配上(就不提之後會出的摺疊屏手機了)。本文總結了當下主流手機的劉海屏適配方案,鑒於目前Android碎片化的情況,想要改寫所有的機型是不可能的,但是能適配一些是一些,總比什麼都不做要好。

所謂劉海屏,指的是手機屏幕正上方由於追求極致邊框而採用的一種手機解決方案。因形似劉海兒而得名——來自百度百科,水滴屏也是類似,為了簡單起見,下文就統稱這兩種為劉海屏了。

什麼時候需要適配

這裡先上一張官方的圖

從圖中可以看出,劉海區域是鑲嵌在狀態欄內部的,劉海區域的高度一般是不超過狀態欄高度的。因此,當我們的應用佈局需要占據狀態欄來顯示時,就需要考慮到劉海區域是否會遮擋住頁面上的控制元件或者背景,這就是為什麼將狀態欄區域稱為危險區域。如果應用不需要占據狀態欄顯示,全部顯示在安全區域內,那麼恭喜你,不需要做任何適配處理。總結來說,只有當應用需要全屏顯示時才需要進行適配。

全屏顯示無非就是兩種情況:第一種是我們常說的沉浸式狀態欄,也就是狀態欄透明,頁面的佈局延伸到狀態欄顯示,這種情況下狀態欄依然可見;第二種是類似應用的閃屏頁風格,頁面全屏顯示,狀態欄不可見。這兩種情況下如果不進行適配處理都會產生一些問題。

先來看第一種情況,沉浸式風格。需要將狀態欄設置為透明,需要註意只有在Android 4.4(API Level 19)以上才支持設置透明狀態欄。有兩種設置方法:

方法一:為Activity設置style,添加一個屬性:

<item name="android:windowTranslucentStatus">trueitem>

 

方法二:在Activity的onCreate()中為Window添加Flag

public class ImmersiveActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_immersive);
        // 透明狀態欄
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            getWindow().addFlags(
                    WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
    }
}

頁面的佈局很簡單,只包含一個按鈕,為了明顯,我為根佈局設置了一個背景。
activity_immersive.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@mipmap/bg"
    android:orientation="vertical">

    <Button
        android:layout_width="150dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />

LinearLayout>

 

運行之後發現按鈕會被劉海區域所遮擋,如圖所示:

再說第二種情況,全屏風格,狀態欄不可見。同樣有兩種設置方法:

方法一:為Activity設置style,添加屬性:

<item name="android:windowFullscreen">trueitem>


<item name=“android:windowBackground”>@mipmap/bgitem>

方法二:在Activity的OnCreate()中添加代碼:

public class FullScreenActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 全屏顯示
        getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN |
                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
    }
}

補充說明一點,現在的手機屏幕高寬比例越來越大,我們還需要額外做一下適配才能使應用在所有手機上都能全屏顯示,具體方式有兩種:

方式一:在AndroidManifest.xml中配置支持最大高寬比

"android.max_aspect"  
    android:value="ratio_float" />

或者

 android:maxAspectRatio="ratio_float" (API LEVEL 26)

說明:以上兩種接口可以二選一,ratio_float = 屏幕高 / 屏幕寬 (如oppo新機型屏幕解析度為2280 x 1080, ratio_float = 2280 / 1080 = 2.11,建議設置 ratio_float為2.2或者更大)

方式二:在AndroidManifest.xml中配置支持分屏,註意驗證分屏下界面兼容性

android:resizeableActivity="true"  

也可以通過設置targetSdkVersion>=24(即Android 7.0),該屬性的值會預設為true,就不需要在AndroidManifest.xml中配置了。

運行之後,我們發現狀態欄的部分留出了一條黑邊,看上起很奇怪,這顯然不是我們想要的效果。

如何適配

上文中已經展示了劉海屏中全屏顯示帶來的問題,那麼如何去解決呢?

1、沉浸式狀態欄的適配

其實沉浸式狀態欄帶來的遮擋問題與劉海屏無關,本質上是由於設置了透明狀態欄導致佈局延伸到了狀態欄中,就算是不具有劉海屏,一定程度上也會造成佈局的遮擋。不過既然劉海屏是處在狀態欄當中的,那麼我們就把這種情況也包含在劉海屏的適配中。清楚了原因之後,解決起來就很簡單了,我們只需要讓控制元件或佈局避開狀態欄顯示就可以了,具體的解決方法有三種。

方法一、利用fitsSystemWindows屬性

當我們給最外層View設置了android:fitsSystemWindows=”true”屬性後,當設置了透明狀態欄或者透明導航欄後,就會自動給View添加paddingTop或paddingBottom屬性,這樣就在屏幕上預留出了狀態欄的高度,我們的佈局就不會占用狀態欄來顯示了。
activity_immersive.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@mipmap/bg"
    android:fitsSystemWindows="true"
    android:orientation="vertical">

    <Button
        android:layout_width="150dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />

LinearLayout>

 

方法二、根據狀態欄高度手動設置paddingTop

這種方法的實現本質上和設置fitsSystemWindows是一樣的,首先獲取狀態欄高度,然後設置根佈局的paddingTop等於狀態欄高度就可以了,代碼如下:

public class ImmersiveActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_immersive);
        // 透明狀態欄
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            getWindow().addFlags(
                    WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
        LinearLayout llRoot = findViewById(R.id.ll_root);
        // 設置根佈局的paddingTop
        llRoot.setPadding(0, getStatusBarHeight(this), 00);
    }

    /**
     * 獲取狀態欄高度
     *
     * @param context
     * @return
     */
    public 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;
    }
}

方法三、在佈局中添加一個和狀態欄高度相同的View

和前兩種方法原理類似,同樣是讓屏幕預留出狀態欄的高度,這裡在根佈局中添加了一個透明的View,高度和狀態欄高度相同。這種方法的好處是可以自定義填充狀態欄View的背景,更靈活地實現我們想要的效果。

public class ImmersiveActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_immersive);
        // 透明狀態欄
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            getWindow().addFlags(
                    WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
        LinearLayout llRoot = findViewById(R.id.ll_root);
        View statusBarView = new View(this);
        statusBarView.setBackgroundColor(Color.TRANSPARENT);
        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                getStatusBarHeight(this));
        // 在根佈局中添加一個狀態欄高度的View
        llRoot.addView(statusBarView, 0, lp);
    }

    /**
     * 獲取狀態欄高度
     *
     * @param context
     * @return
     */
    public 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;
    }
}

適配之後成功地將控制元件避開了狀態欄(危險區域),如下圖所示:

2、全屏顯示的適配

對於全屏顯示的情況,處理起來要相對麻煩一些,下麵重點說一下這種情況下的適配方案。

2.1.Android P及以上

谷歌官方從Android P開始給開發者提供了劉海屏相關的API,可以通過直接呼叫API來進行劉海屏的適配處理。
通過DisplayCutout類可以獲得安全區域的範圍以及劉海區域(官方的叫法是缺口)的信息,需要註意只有API Level在28及以上才可以呼叫。

/**
 * 獲得劉海區域信息
 */
@TargetApi(28)
public void getNotchParams() {
    final View decorView = getWindow().getDecorView();
    if (decorView != null) {
        decorView.post(new Runnable() {
            @Override
            public void run() {
                WindowInsets windowInsets = decorView.getRootWindowInsets();
                if (windowInsets != null) {
                    // 當全屏頂部顯示黑邊時,getDisplayCutout()傳回為null
                    DisplayCutout displayCutout = windowInsets.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 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);
                        }
                    }
                }
            }
        });
    }
}

這裡我在測試時也發現了一個問題,就是如果是在style中設置了全屏樣式,在適配之前,頂部狀態欄區域顯示一條黑邊,這時候呼叫getDisplayCutout()獲取DisplayCutout物件傳回的結果是null,其實這也不難理解,因為這時候是看不出劉海區域的,但是這樣會導致在適配之前無法通過DisplayCutout判斷是否存在劉海屏,只能在適配後才能獲取到劉海區域信息,因此只能對於所有設備都添加適配代碼。

那麼接下來如何進行適配呢,Android P中增加了一個視窗佈局引數屬性layoutInDisplayCutoutMode,該屬性有三個值可以取:

LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:預設的佈局樣式,僅當劉海區域完全包含在狀態欄之中時,才允許視窗延伸到劉海區域顯示,也就是說,如果沒有設置為全屏顯示樣式,就允許視窗延伸到劉海區域,否則不允許。

LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER:永遠不允許視窗延伸到劉海區域。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES:始終允許視窗延伸到屏幕短邊上的劉海區域,視窗永遠不會延伸到屏幕長邊上的劉海區域。

還有一個LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS樣式,目前已經被LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES所取代,不允許使用了,這裡就不提了。

這麼看可能還是有些不理解,接下來我們在一個全屏顯示的頁面分別設置三種佈局樣式,看看有什麼區別。

public class FullScreenActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            WindowManager.LayoutParams lp = getWindow().getAttributes();
            // 僅當缺口區域完全包含在狀態欄之中時,才允許視窗延伸到劉海區域顯示
//            lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
            // 永遠不允許視窗延伸到劉海區域
//            lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
            // 始終允許視窗延伸到屏幕短邊上的劉海區域
            lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
            getWindow().setAttributes(lp);
        }
    }
}

三種樣式下的顯示效果如下圖所示:

LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT

LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER

LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES

可以看出,當在全屏顯示情況下,LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT和LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER的效果是一樣的,都是在狀態欄顯示一條黑邊,也就是不允許視窗佈局延伸到劉海區域,而LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES則允許視窗佈局延伸到了劉海區域,這裡需要註意是短邊劉海區域,不過一般市面上的手機劉海區域都是在短邊上的,我是沒見過劉海長在“腰”上的,因此利用這個樣式就實現適配了。

通過之前沉浸式狀態欄的顯示效果可以看出,LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT在此時是允許視窗佈局延伸到劉海區域的,因此更證實了只有在全屏顯示的情況下該樣式才不允許視窗佈局延伸到劉海區域。

適配後效果如下,現在看起來就很舒服了:

我這裡為了簡單沒有添加任何控制元件,實際開發中在全屏顯示後我們仍然需要考慮劉海區域是否會遮擋顯示的內容和控制元件,同樣需要避開危險區域來顯示。做法和沉浸式狀態欄的適配相同,原理同樣是將佈局下移,預留出狀態欄的高度,這裡就不一一列舉了。

2.2 Android P以下

目前市面上的劉海屏手機可以說是琳琅滿目,各大廠商都在追求極致的屏占比,推出的新機型也基本上都有劉海屏,針對Android P以下的手機,我們只能依照各個廠商提供的適配方案來進行適配。我也查閱了網上的一些適配文章,主要還是針對目前主流的手機品牌,本文總結了華為、小米、Vivo和Oppo的適配方案,其他品牌的手機之後有時間的話可能會再考慮。

華為適配方案

華為官方提供的適配文件:華為劉海屏手機安卓O版本適配指導https://developer.huawei.com/consumer/cn/devservice/doc/50114

文件中提供了很多劉海屏相關的方法,這裡就不一一列舉了,著重看一下我們需要用到的方法。

判斷是否有劉海屏

 /**
 * 判斷是否有劉海屏
 *
 * @param context
 * @return true:有劉海屏;false:沒有劉海屏
 */
public static boolean hasNotch(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 (ClassNotFoundException e) {
        Log.e("test""hasNotchInScreen ClassNotFoundException");
    } catch (NoSuchMethodException e) {
        Log.e("test""hasNotchInScreen NoSuchMethodException");
    } catch (Exception e) {
        Log.e("test""hasNotchInScreen Exception");
    } finally {
        return ret;
    }
}

應用頁面設置使用劉海區顯示

官方提供了兩種適配方案:

方案一.使用新增的meta-data屬性android.notch_support,在應用的AndroidManifest.xml中增加meta-data屬性,此屬性不僅可以針對Application生效,也可以對Activity配置生效。

使用方式如下:

"android.notch_support" android:value="true"/>

可以在Application下添加,意味著該應用的所有頁面,系統都不會做豎屏場景的特殊下移或者是橫屏場景的右移特殊處理。

    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
            android:name="android.notch_support"
        android:value="true" />
     ...
</application>

也可以針對指定的Activity添加,意味著可以針對單個頁面進行劉海屏適配,設置了該屬性的Activity系統將不會做特殊處理。


<activity
    android:name=".ui.FullScreenActivity"
    android:screenOrientation="portrait"
    android:theme="@style/FullScreenTheme">
    <meta-data
        android:name="android.notch_support"
        android:value="true" />
activity>

 

方案二.使用給window添加新增的FLAG_NOTCH_SUPPORT
代碼如下:

/**
 * 設置應用視窗在劉海屏手機使用劉海區
 * <p>
 * 通過添加視窗FLAG的方式設置頁面使用劉海區顯示
 *
 * @param window 應用頁面window物件
 */
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(WindowManager.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");
    }
}

 

官方提供的所有方法我已經放到了工具類HwNotchUtils里,可以根據需求來使用。

https://github.com/StephenZKCurry/NotchAdaptedTest/blob/master/app/src/main/java/com/example/notchadaptedtest/utils/HwNotchUtils.java

小米適配方案

小米官方提供的適配文件:https://dev.mi.com/console/doc/detail?pId=1293

我們同樣看一下關鍵方法。

判斷是否有劉海屏

/**
 * 判斷是否有劉海屏
 *
 * @param context
 * @return true:有劉海屏;false:沒有劉海屏
 */
public static boolean hasNotch(Context context) {
    boolean ret = false;
    try {
        ClassLoader cl = context.getClassLoader();
        Class SystemProperties = cl.loadClass("android.os.SystemProperties");
        Method get = SystemProperties.getMethod("getInt", String.class, int.class);
        ret = (Integer) get.invoke(SystemProperties, "ro.miui.notch"0) == 1;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        return ret;
    }
}

應用頁面設置使用劉海區顯示

小米提供的適配方案同樣有兩種(meta-data和Flag),使用方法和華為類似。

方案一.Application級別的控制接口

在 Application 下增加一個 meta-data,用以宣告該應用視窗是否可以延伸到狀態欄。

    android:name="notch.config"
    android:value="portrait|landscape"/>

其中,value的值可以是以下四種

"none" 橫豎屏都不繪製耳朵區

"portrait" 豎屏繪製到耳朵區

"landscape" 橫屏繪製到耳朵區

"portrait|landscape" 橫豎屏都繪製到耳朵區

這裡的耳朵區指的就是劉海區兩側的狀態欄區域

雖然官方文件上說的是Application級別的,但是我覺得也可以針對某一個Activity來配置,不過由於手頭上的手機條件不滿足,我並沒有驗證,如果有小伙伴測試過的話可以反饋一下,我再修正一下這裡的說法。

方案二.Window級別的控制接口

通過給Window添加Flag也可以實現將視窗佈局延伸到狀態欄中顯示。

/*劉海屏全屏顯示FLAG*/
public static final int FLAG_NOTCH_SUPPORT = 0x00000100// 開啟配置
public static final int FLAG_NOTCH_PORTRAIT = 0x00000200// 豎屏配置
public static final int FLAG_NOTCH_HORIZONTAL = 0x00000400// 橫屏配置

/**
 * 設置應用視窗在劉海屏手機使用劉海區
 * 

* 通過添加視窗FLAG的方式設置頁面使用劉海區顯示
*
@param window 應用頁面window物件
*/

 


public static void setFullScreenWindowLayoutInDisplayCutout(Window window) {
    // 豎屏繪製到耳朵區
    int flag = FLAG_NOTCH_SUPPORT | FLAG_NOTCH_PORTRAIT;
    try {
        Method method = Window.class.getMethod("addExtraFlags",
                int.class);
        method.invoke(window, flag);
    } catch (Exception e) {
        Log.e("test""addExtraFlags not found.");
    }
}

官方提供的所有方法我已經放到了工具類XiaomiNotchUtils里,可以根據需求來使用。
這裡說一下我的測試情況,我是用小米8測試的,系統版本已經升到了Android P,利用小米官方提供的適配方法沒有效果,只能用谷歌官方針對Android P的適配方案,這一點小米的官方文件也提到了。

至於Android P以下版本的小米手機,我並沒有測試,如果有哪位大佬測試過了發現有問題可以反饋一下。

Vivo、Oppo適配方案

Vivo官方提供的適配文件:Vivo全面屏應用適配指南https://dev.vivo.com.cn/documentCenter/doc/103

Oppo官方提供的適配文件:Oppo凹形屏適配指南https://open.oppomobile.com/wiki/doc#id=10159

這裡把Vivo和Oppo放在一起說,官方提供的資料不像華為和小米那麼詳細,只是提供了判斷是否有劉海屏的方法。

Vivo判斷是否有劉海屏

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

/**
 * 判斷是否有劉海屏
 *
 * @param context
 * @return true:有劉海屏;false:沒有劉海屏
 */
public static boolean hasNotch(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;
    }
}

Oppo判斷是否有劉海屏

/**
 * 判斷是否有劉海屏
 *
 * @param context
 * @return true:有劉海屏;false:沒有劉海屏
 */
public static boolean hasNotch(Context context) {
    return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
}

至於全屏顯示的適配方案,通過閱讀官方文件和網上的其他適配文章,我個人總結一下就是這兩種品牌的手機在設置全屏顯示時都無需做任何處理(前提是適配了全面屏,上文中提到過如何配置),也就是不會產生黑邊,我們只需要避免佈局中的內容或控制元件不被劉海區域所遮擋就可以了。具體的做法和沉浸式狀態欄的適配相同,基本原理還是將視窗佈局下移,預留出狀態欄的高度。

註:由於手頭沒有這兩種廠商的手機,因此並沒有驗證,這一點確實是我做得不夠嚴謹,有好心的大佬驗證之後歡迎指正。

其實我本來也想列出魅族的適配方案的,但是實在是沒找到官方文件。。。如果有知道的大佬可以提供一下,我後面會把適配方案補上。

適配時的基本邏輯就是先判斷手機的品牌,這裡我利用了一個開源工具類專案AndroidUtilCode,提供了一個獲取手機Rom信息的工具類RomUtils,用起來很方便,然後判斷是否是劉海屏,針對劉海屏手機添加適配代碼。完整的適配代碼如下所示:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    // Android P利用官方提供的API適配
    WindowManager.LayoutParams lp = getWindow().getAttributes();
    // 始終允許視窗延伸到屏幕短邊上的缺口區域
    lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
    getWindow().setAttributes(lp);
} else {
    // Android P以下根據手機廠商的適配方案進行適配
    if (RomUtils.isHuawei() && HwNotchUtils.hasNotch(this)) {
        HwNotchUtils.setFullScreenWindowLayoutInDisplayCutout(getWindow());
    } else if (RomUtils.isXiaomi() && XiaomiNotchUtils.hasNotch(this)) {
        XiaomiNotchUtils.setFullScreenWindowLayoutInDisplayCutout(getWindow());
    }
}

總結

雖然文中介紹了很多適配的內容,但其實在開發中需要我們適配劉海屏的情況並不多,只有兩種情況需要我們進行考慮:

1、沉浸式狀態欄,視窗佈局延伸到了狀態欄中,是否會遮擋必要的內容或控制元件(處在危險區域)。適配方案就是將視窗佈局下移,預留出狀態欄的空間。

2、全屏顯示樣式,不做適配的話狀態欄會呈現一條黑邊。適配方案是首先判斷系統版本,是Android P及以上就按照官方的API來適配,否則根據手機廠商的適配方案進行適配。鑒於目前市面上Android P還沒有普及,為了帶來更好的用戶體驗,我們還是需要多花一些精力來適配各個手機廠商的劉海屏手機。

最後提示一下,本文只列出了四個當下主流手機廠商的適配方案,我自己驗證過的只有華為和小米(只驗證了Android P)的方案,對於Vivo和Oppo的一些結論我可能說得不對,歡迎大家指正。當然,如果大家還需要其他廠商的適配方案,也歡迎提出,我會儘力補上。

相關的代碼和工具類我已經上傳到了github,可以下載Demo來查看,大家一起交流https://github.com/StephenZKCurry/NotchAdaptedTest

參考資料

  • Android 劉海屏適配全攻略
  • https://blog.csdn.net/u011810352/article/details/80587531
  • 華為劉海屏手機安卓O版本適配指導
  • https://developer.huawei.com/consumer/cn/devservice/doc/50114
  • 小米劉海屏水滴屏 Android O 適配
  • https://dev.mi.com/console/doc/detail?pId=1293
  • Vivo全面屏應用適配指南
  • https://dev.vivo.com.cn/documentCenter/doc/103
  • Oppo凹形屏適配指南
  • https://open.oppomobile.com/wiki/doc#id=10159

赞(0)

分享創造快樂