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

無需再怨恨“劉海屏”了,因為適配十分簡單

作者:王英豪

鏈接:https://www.jianshu.com/p/c9e710a9fa35

網上關於劉海屏適配的文章不少,可講清楚的卻沒幾篇,大多是拷貝文件、長篇大論,甚至熱情的貼圖告訴你什麼是劉海屏,到最後你仍不確定到底是怎樣的一個適配方案,才能讓你的 app 真正的適配所有的劉海屏機型。

看到這篇文章你就無需再怨恨各大廠商的跟風“劉海”了,因為劉海屏的適配十分簡單。

ok,廢話說完了,開始適配。

首先要清楚的是哪些界面需要適配劉海屏:

  • 有狀態欄的界面:劉海區域會顯示狀態欄,無需適配

  • 全屏界面:劉海區域可能遮擋內容,需要適配

如果你的應用里所有界面都有狀態欄,那麼恭喜你,你不用做任何操作,狀態欄就那麼自然的顯示在劉海區域,毫無違和,劉海屏已適配完畢,可以點叉出去了。

不幸的是,你的應用中很大幾率會有全屏界面,所謂的劉海屏適配,也正是針對這些全屏界面

如果你什麼都不做,預設規則不允許全屏界面內容顯示到劉海區域,即劉海屏區域會保留一條黑邊,你的全屏界面會在劉海下方展示,這看起來好像也是可以接受的,然後你竟說服產品達成共識,“無為而治”才是最強大的劉海屏適配方案!

但有些手機廠商(譬如oppo)不開心了,我辛辛苦苦研發的劉海屏手機,你們這些開發者竟直接放棄劉海區域!然後就在你的全屏界面下方加了一條提示:“全屏顯示”,當用戶點擊開啟後,強行把你的全屏界面顯示到劉海區域,然後一切都亂套了…

嗯~ “無為而治”行不通。

只能允許全屏界面內容顯示到劉海區域了,參考各大廠商的適配文件,我們可以知道如何允許,比如華為機型只需在 AndroidManifest 中配置:

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

配置後,華為機型上的全屏界面就會顯示到劉海區域了,但這個劉海,是可能擋住我們全屏界面中的內容的。這時需要將全屏界面中的視圖元素適當下移,保證不會被劉海遮擋住,就 ok 了。

這裡我們搞清楚:允許全屏界面內容顯示到劉海區域的機型,才需要將全屏界面中的視圖元素適當下移。

比如若只允許華為機型全屏界面內容顯示到劉海區域,那隻有華為的劉海屏機型才需要將全屏界面中的視圖元素適當下移,其他廠商的劉海屏機型則不需要下移。

如果允許華為、小米、oppo、vivo 全屏界面內容顯示到劉海區域,那麼華為、小米、oppo、vivo 劉海屏機型需要將全屏界面中的視圖元素適當下移。

另外也不一定要通過全屏界面中的視圖元素適當下移方式來適配劉海屏,如果產品形態允許的話,你也可以讓該界面顯示狀態欄啊。

至此劉海屏適配完畢,是不是很簡單!?

最後代碼奉上,拿走不謝:

1、允許全屏界面內容顯示到劉海區域配置:


<meta-data
    android:name="android.max_aspect"
    android:value="2.2" />



<meta-data
    android:name="android.notch_support"
    android:value="true" />



<meta-data
    android:name="notch.config"
    android:value="portrait" />

上面在 AndroidManifest 的配置在 Android 9.0 之前有效,9.0 系統針對劉海屏制定了新的 api,預設保留一條黑邊,即不允許繪製到劉海區域。所以如果你還沒有適配 Android 9.0,那在判斷是否是允許全屏界面內容顯示到劉海區域的劉海屏機型時,就要加上版本判斷。

2、判斷是否是允許全屏界面內容顯示到劉海區域的劉海屏機型:

public class CutoutUtil {

    private static Boolean sAllowDisplayToCutout;

    /**
     * 是否為允許全屏界面顯示內容到劉海區域的劉海屏機型(與AndroidManifest中配置對應)
     */

    public static boolean allowDisplayToCutout() {
        if (sAllowDisplayToCutout == null) {
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O_MR1) {
                // 9.0系統全屏界面預設會保留黑邊,不允許顯示內容到劉海區域
                return sAllowDisplayToCutoutDevice = false;
            }
            Context context = App.get();
            if (hasCutout_Huawei(context)) {
                return sAllowDisplayToCutout = true;
            }
            if (hasCutout_OPPO(context)) {
                return sAllowDisplayToCutout = true;
            }
            if (hasCutout_VIVO(context)) {
                return sAllowDisplayToCutout = true;
            }
            if (hasCutout_XIAOMI(context)) {
                return sAllowDisplayToCutout = true;
            }
            return sAllowDisplayToCutout = false;
        } else {
            return sAllowDisplayToCutout;
        }
    }


    /**
     * 是否是華為劉海屏機型
     */

    @SuppressWarnings("unchecked")
    private static boolean hasCutout_Huawei(Context context) {
        if (!Build.MANUFACTURER.equalsIgnoreCase("HUAWEI")) {
            return false;
        }
        try {
            ClassLoader cl = context.getClassLoader();
            Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
            if (HwNotchSizeUtil != null) {
                Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen");
                return (boolean) get.invoke(HwNotchSizeUtil);
            }
            return false;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 是否是oppo劉海屏機型
     */

    @SuppressWarnings("unchecked")
    private static boolean hasCutout_OPPO(Context context) {
        if (!Build.MANUFACTURER.equalsIgnoreCase("oppo")) {
            return false;
        }
        return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
    }

    /**
     * 是否是vivo劉海屏機型
     */

    @SuppressWarnings("unchecked")
    private static boolean hasCutout_VIVO(Context context) {
        if (!Build.MANUFACTURER.equalsIgnoreCase("vivo")) {
            return false;
        }
        try {
            ClassLoader cl = context.getClassLoader();
            @SuppressLint("PrivateApi")
            Class ftFeatureUtil = cl.loadClass("android.util.FtFeature");
            if (ftFeatureUtil != null) {
                Method get = ftFeatureUtil.getMethod("isFeatureSupport", int.class);
                return (boolean) get.invoke(ftFeatureUtil, 0x00000020);
            }
            return false;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 是否是小米劉海屏機型
     */

    @SuppressWarnings("unchecked")
    private static boolean hasCutout_XIAOMI(Context context) {
        if (!Build.MANUFACTURER.equalsIgnoreCase("xiaomi")) {
            return false;
        }
        try {
            ClassLoader cl = context.getClassLoader();
            @SuppressLint("PrivateApi")
            Class SystemProperties = cl.loadClass("android.os.SystemProperties");
            Class[] paramTypes = new Class[2];
            paramTypes[0] = String.class;
            paramTypes[1] = int.class;
            Method getInt = SystemProperties.getMethod("getInt", paramTypes);
            //引數
            Object[] params = new Object[2];
            params[0] = "ro.miui.notch";
            params[1] = 0;
            return (Integer) getInt.invoke(SystemProperties, params) == 1;
        } catch (Exception e) {
            return false;
        }
    }

}

上面提到,不一定要通過全屏界面中的視圖元素適當下移方式來適配劉海屏,如果產品形態允許的話,也可以讓該界面顯示狀態欄。

顯示狀態欄的方案是較為通用簡單的,或者說,在一個應用中,一些全屏界面往往是允許使用顯示狀態欄的方案來適配的,如果你考慮使用這種方案,那便會是這種效果:

在你的應用中,你期望某些全屏界面在劉海屏機型上必須全屏展示,那你就自行將界面元素適當下移,從而避免被劉海遮擋;而某些全屏界面不是非要全屏顯示,允許在劉海屏機型顯示狀態欄,那就通過顯示狀態欄的方式,從而避免被劉海遮擋。

為了實現這種效果,我們需要標記區分哪些界面必須全屏展示、哪些界面允許顯示狀態欄。這裡提供一種實現方式,讓允許顯示狀態欄的界面 Activity 繼承一個接口,比如:

public interface CutoutAdapt {
}


然後在 ActivityLifecycleCallbacks 回呼,統一適配允許通過顯示狀態欄的全屏界面:

@Override
public void onActivityStarted(Activity activity) {
    // 如果是允許全屏顯示到劉海屏區域的劉海屏機型
    if (CutoutUtil.allowDisplayToCutout()) {
        if (isFullScreen(activity)) {
            // 如果允許通過顯示狀態欄方式適配劉海屏
            if (activity instanceof CutoutAdapt) {
                // 顯示狀態欄
                StatusBarUtil.showStatusbar(activity.getWindow());
            } else {
                // 需自行將該界面視圖元素下移,否則可能會被劉海遮擋
            }
        } else {
            // 非全屏界面無需適配劉海屏
        }
    }
}


●編號463,輸入編號直達本文

●輸入m獲取到文章目錄

推薦↓↓↓

Java編程

更多推薦25個技術類公眾微信

涵蓋:程式人生、演算法與資料結構、黑客技術與網絡安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。