Android劉海屏、水滴屏全面屏適配方案

聰明叉發表於2019-06-04

我將適配方案整理後,封裝成了一個庫並上傳至github,可參考使用

專案地址: github.com/smarxpan/No…

市面上的螢幕尺寸和全面屏方案五花八門。

這裡我使用了小米的圖來說明:

Android劉海屏、水滴屏全面屏適配方案

上述兩種螢幕都可以統稱為劉海屏,不過對於右側較小的劉海,業界一般稱為水滴屏或美人尖。為便於說明,後文提到的「劉海屏」「劉海區」都同時指代上圖兩種螢幕。

當我們在談螢幕適配時,我們在談什麼

  1. 適應更長的螢幕
  2. 防止內容被劉海遮擋

其中第一點是所有應用都需要適配的,對應下文的宣告最大長寬比

而第二點,如果應用本身不需要全屏顯示或使用沉浸式狀態列,是不需要適配的。

針對需要適配第二點的應用,需要獲取劉海的位置和寬高,然後將顯示內容避開即可。

宣告最大長寬比

以前的普通屏長寬比為16:9,全面屏手機的螢幕長寬比增大了很多,如果不適配的話就會類似下面這樣:

Android劉海屏、水滴屏全面屏適配方案

黑色區域為未利用的區域。

適配方式

適配方式有兩種:

  1. 將targetSdkVersion版本設定到API 24及以上

    這個操作將會為<application> 標籤隱式新增一個屬性,android:resizeableActivity="true", 該屬性的作用後面將詳細說明。

  2. <application> 標籤中增加屬性:android:resizeableActivity="false"

    同時在節點下增加一個meta-data標籤:

     <!-- Render on full screen up to screen aspect ratio of 2.4 -->
     <!-- Use a letterbox on screens larger than 2.4 -->
     <meta-data android:name="android.max_aspect" android:value="2.4" />
    複製程式碼

原理說明

這裡涉及到的知識點是android:resizeableActivity屬性。

在 Android 7.0(API 級別 24)或更高版本的應用,android:resizeableActivity屬性預設為true(對應適配方式1)。這個屬性是控制多視窗顯示的,決定當前的應用或者Activity是否支援多視窗。

多視窗支援

在清單的<activity><application>節點中設定該屬性,啟用或禁用多視窗顯示:

android:resizeableActivity=["true" | "false"]
複製程式碼

如果該屬性設定為 true,Activity 將能以分屏和自由形狀模式啟動。 如果此屬性設定為 false,Activity 將不支援多視窗模式。 如果該值為 false,且使用者嘗試在多視窗模式下啟動 Activity,該 Activity 將全屏顯示。

適配方式2即為設定螢幕的最大長寬比,這是官方提供的設定方式。

如果設定了最大長寬比,必須android:resizeableActivity="false"。 否則最大長寬比沒有任何作用。

適配劉海屏

Android9.0及以上適配

Android P(9.0)開始,官方提供了適配異形屏的方式。

Support display cutouts

通過全新的 DisplayCutout 類,可以確定非功能區域的位置和形狀,這些區域不應顯示內容。 要確定這些凹口螢幕區域是否存在及其位置,請使用 getDisplayCutout() 函式。

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

    • LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
    • LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
    • LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER

    預設值是LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,劉海區域不會顯示內容,需要將值設定為LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES

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

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

     // 延伸顯示區域到劉海
     WindowManager.LayoutParams lp = window.getAttributes();
     lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
     window.setAttributes(lp);
     // 設定頁面全屏顯示
     final View decorView = window.getDecorView();
     decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
    複製程式碼

    其中延伸顯示區域到劉海的程式碼,也可以通過修改Activity或應用的style實現,例如:

     <?xml version="1.0" encoding="utf-8"?>
     <resources>
         <style name="AppTheme" parent="xxx">
             <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
         </style>
     </resources>
    複製程式碼

Android O 適配

因Google官方的適配方案到Android P才推出,因此在Android O裝置上,各家廠商有自己的實現方案。

我這裡主要適配了華為、小米、oppo,這三家都給了完整的解決方案。至於vivo,vivo給了判斷是否劉海屏的API,但是沒用設定劉海區域顯示到API,因此無需適配。

適配華為Android O裝置

方案一:

  1. 具體方式如下所示:

     <meta-data android:name="android.notch_support" android:value="true"/>
    複製程式碼
  2. 對Application生效,意味著該應用的所有頁面,系統都不會做豎屏場景的特殊下移或者是橫屏場景的右移特殊處理:

     <application
         android:allowBackup="true"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
         android:roundIcon="@mipmap/ic_launcher_round"
         android:testOnly="false"
         android:supportsRtl="true"
         android:theme="@style/AppTheme">
         <meta-data android:name="android.notch_support" android:value="true"/>
         <activity android:name=".MainActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
     </activity>
    複製程式碼
  3. 對Activity生效,意味著可以針對單個頁面進行劉海屏適配,設定了該屬性的Activity系統將不會做特殊處理:

     <application
         android:allowBackup="true"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
         android:roundIcon="@mipmap/ic_launcher_round"
         android:testOnly="false"
         android:supportsRtl="true"
         android:theme="@style/AppTheme">
         <activity android:name=".MainActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
     
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".LandscapeFullScreenActivity" android:screenOrientation="sensor">
         </activity>
         <activity android:name=".FullScreenActivity">
             <meta-data android:name="android.notch_support" android:value="true"/>
         </activity>
     </application>
    複製程式碼

方案二

對Application生效,意味著該應用的所有頁面,系統都不會做豎屏場景的特殊下移或者是橫屏場景的右移特殊處理

我的NotchScreenTool中使用的就是方案二,如果需要針對Activity,建議自行修改。

  1. 設定應用視窗在華為劉海屏手機使用劉海區

     /*劉海屏全屏顯示FLAG*/
     public static final int FLAG_NOTCH_SUPPORT=0x00010000;
     /**
      * 設定應用視窗在華為劉海屏手機使用劉海區
      * @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(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");
         }
     }
    複製程式碼
  2. 清除新增的華為劉海屏Flag,恢復應用不使用劉海區顯示

     /**
      * 設定應用視窗在華為劉海屏手機使用劉海區
      * @param window 應用頁面window物件
      */
     public static void setNotFullScreenWindowLayoutInDisplayCutout (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(LayoutParams.class);
             Object layoutParamsExObj=con.newInstance(layoutParams);
             Method method=layoutParamsExCls.getMethod("clearHwFlags", int.class);
             method.invoke(layoutParamsExObj, FLAG_NOTCH_SUPPORT);
         } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException |InstantiationException 
         | InvocationTargetException e) {
             Log.e("test", "hw clear notch screen flag api error");
         } catch (Exception e) {
             Log.e("test", "other Exception");
         }
     }
    複製程式碼

適配小米Android O裝置

  1. 判斷是否是劉海屏

     private static boolean isNotch() {
         try {
             Method getInt = Class.forName("android.os.SystemProperties").getMethod("getInt", String.class, int.class);
             int notch = (int) getInt.invoke(null, "ro.miui.notch", 0);
             return notch == 1;
         } catch (Throwable ignore) {
         }
         return false;
     }
    複製程式碼
  2. 設定顯示到劉海區域

     @Override
     public void setDisplayInNotch(Activity activity) {
         int flag = 0x00000100 | 0x00000200 | 0x00000400;
         try {
             Method method = Window.class.getMethod("addExtraFlags",
                     int.class);
             method.invoke(activity.getWindow(), flag);
         } catch (Exception ignore) {
         }
     }
    複製程式碼
  3. 獲取劉海寬高

     public static int getNotchHeight(Context context) {
         int resourceId = context.getResources().getIdentifier("notch_height", "dimen", "android");
         if (resourceId > 0) {
             return context.getResources().getDimensionPixelSize(resourceId);
         }
         return 0;
     }
    
     public static int getNotchWidth(Context context) {
         int resourceId = context.getResources().getIdentifier("notch_width", "dimen", "android");
         if (resourceId > 0) {
             return context.getResources().getDimensionPixelSize(resourceId);
         }
         return 0;
     }
    複製程式碼

適配oppoAndroid O裝置

  1. 判斷是否是劉海屏

     @Override
     public boolean hasNotch(Activity activity) {
         boolean ret = false;
         try {
             ret = activity.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
         } catch (Throwable ignore) {
         }
         return ret;
     }
    複製程式碼
  2. 獲取劉海的左上角和右下角的座標

     /**
      * 獲取劉海的座標
      * <p>
      * 屬性形如:[ro.oppo.screen.heteromorphism]: [378,0:702,80]
      * <p>
      * 獲取到的值為378,0:702,80
      * <p>
      * <p>
      * (378,0)是劉海區域左上角的座標
      * <p>
      * (702,80)是劉海區域右下角的座標
      */
     private static String getScreenValue() {
         String value = "";
         Class<?> cls;
         try {
             cls = Class.forName("android.os.SystemProperties");
             Method get = cls.getMethod("get", String.class);
             Object object = cls.newInstance();
             value = (String) get.invoke(object, "ro.oppo.screen.heteromorphism");
         } catch (Throwable ignore) {
         }
         return value;
     }
    複製程式碼

Oppo Android O機型不需要設定顯示到劉海區域,只要設定了應用全屏就會預設顯示。

因此Oppo機型必須適配。

適配總結

根據上述功能,我將其整理成了一個依賴庫:NotchScreenTool

使用起來很簡單:

// 支援顯示到劉海區域
NotchScreenManager.getInstance().setDisplayInNotch(this);
// 獲取劉海屏資訊
NotchScreenManager.getInstance().getNotchInfo(this, new INotchScreen.NotchScreenCallback() {
    @Override
    public void onResult(INotchScreen.NotchScreenInfo notchScreenInfo) {
        Log.i(TAG, "Is this screen notch? " + notchScreenInfo.hasNotch);
        if (notchScreenInfo.hasNotch) {
            for (Rect rect : notchScreenInfo.notchRects) {
                Log.i(TAG, "notch screen Rect =  " + rect.toShortString());
            }
        }
    }
});
複製程式碼

獲取劉海區域資訊後就可以根據自己應用的需要,來避開重要的控制元件。

詳情可參考我專案中的程式碼。

參考連結

宣告受限螢幕支援:宣告最大長寬比

Android 8.1 相容性定義

多視窗支援

Support display cutouts

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

OPPO凹形屏適配說明

vivo 全面屏應用適配指南

小米劉海屏水滴屏 Android O 適配

相關文章