随着iPhone X釋出,國内一些廠商也推出了劉海屏手機,即将釋出的Android p也提供了對劉海屏的支援。so,我們的app也要提前做好适配。
什麼是劉海屏?
螢幕的正上方居中位置(下圖黑色區域)會被挖掉一個孔,螢幕被挖掉的區域無法正常顯示内容,這種類型的螢幕就是劉海屏,也有其他叫法:挖孔屏、凹凸屏等等,這裡統一按劉海屏命名。

OPPO R15.png
目前國内廠商已經推出的劉海屏Android手機有華為P20 pro, vivo X21,OPPO R15。
如果沒有适配劉海屏會有什麼後果?
後果一:導航欄中title被遮擋

未适配劉海屏1
後果二:顯示内容下移,頭部出現黑條,底部出現遮擋

未适配劉海屏2
如何适配劉海屏?
由于Android p正式版今日剛釋出, 目前市面上的Android 劉海屏手機還不能用Android 官方提供的方案來解決,那怎麼辦呢?還好幾個廠商自己給出了适配方案。我們先講理論後上代碼,如果您隻想要代碼就直接往下翻:
華為P20 pro
華為劉海屏适配官方文檔
華為給出的文檔最為詳細(業界良心),P20 pro預裝系統對未做劉海屏适配處理的app有一定處理,處理邏輯如下

預處理流程圖
可見,會被華為系統做偏移處理的有2種情況:
1.未設定meta-data值,頁面橫屏狀态
2.未設定meta-data值,頁面豎屏狀态,不顯示狀态欄
這2種情況都會出現後果二。如果你的app中頁面沒有這兩種情況,例如都是豎屏且顯示狀态欄,你就可以淡定地不做處理。
現在我們知道原因了就可以對症下藥了,這裡給出我推薦的解決方案(官方給出的解決方案不止一種,可以根據自己的需要采用) 分為4步:
1.配置meta-data
①對Application生效,意味着該應用的所有頁面,系統都不會做豎屏場景的特殊下移或者是橫屏場景的右移特殊處理:

配置在application下
② 對Activity生效,意味着可以針對單個頁面進行劉海屏适配,設定了該屬性的Activity系統将不會做特殊處理:

配置在Activity下
2.檢測是否存在劉海屏
public static boolean hasNotchInScreen(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;
}
}
3.擷取劉海屏的參數
public static int[] getNotchSize(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("test", "getNotchSize ClassNotFoundException");
} catch (NoSuchMethodException e) {
Log.e("test", "getNotchSize NoSuchMethodException");
} catch (Exception e) {
Log.e("test", "getNotchSize Exception");
} finally {
return ret;
}
}
4. UI适配
沒錯,第1步僅僅是告訴EMUI系統不要瞎操作你的頁面,真正适配的活還要你自己幹。
①判斷是否劉海屏,代碼上面給出了
②如果是劉海屏手機需要應用自己調整布局避開劉海區,布局原則:保證重要的文字、圖檔和視訊資訊、可點選的控件和圖示還有應用彈窗等等布局建議顯示在狀态欄區域以下(安全區域);不重要,遮擋不會出現問題的布局可以延伸到狀态欄區域(危險區域)顯示,按照這種布局原則修改,可以一次修改就能适配所有的劉海屏手機:

UI适配
vivo & OPPO
vivo 和 OPPO官網僅僅給出了适配指導,沒有給出具體方案,簡單總結為:
如有是具有劉海屏的手機,豎屏顯示狀态欄,橫屏不要在危險區顯示重要資訊或者設定點選事件。
那怎麼知道是不是劉海屏手機呢?
OPPO判斷方法:
public static boolean hasNotchInOppo(Context context){
return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
}
vivo的判斷方法:
public static final int NOTCH_IN_SCREEN_VOIO=0x00000020;//是否有凹槽
public static final int ROUNDED_IN_SCREEN_VOIO=0x00000008;//是否有圓角
public static boolean hasNotchInScreenAtVoio(Context context){
boolean ret = false;
try {
ClassLoader cl = context.getClassLoader();
Class FtFeature = cl.loadClass("com.util.FtFeature");
Method get = FtFeature.getMethod("isFeatureSupport",int.class);
ret = (boolean) get.invoke(FtFeature,NOTCH_IN_SCREEN_VOIO);
} 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; }
}
例如圖一是在OPPO R15上出現的title被遮擋,顯示狀态欄後顯示效果如下:

顯示狀态欄後
以上所講的是各廠商提供的解決方案, 那麼google官方對劉海屏做了什麼樣的支援呢?
google官方劉海屏适配方案
google從Android P開始為劉海屏提供支援,目前提供了一個類和三種模式:
一個類
The new DisplayCutout class lets you find out the location and shape of the non-functional areas where content shouldn't be displayed. To determine the existence and placement of these cutout areas, use thegetDisplayCutout() method
就是說可以用DisplayCutout這個類找出劉海(cutout)的位置和形狀,調用getDisplayCutout()這個方法可以擷取劉海(cutout)的位置和區域,我們看看這個類提供了什麼方法:

DisplayCutout提供的方法.png
是以我們可用這個類判斷是否有劉海的存在以及劉海的位置
DisplayCutout cutout = mContext.getDisplayCutout();
三種模式
A new window layout attribute, layoutInDisplayCutoutMode, allows your app to lay out its content around a device's cutouts. You can set this attribute to one of the following values:
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
第一種模式:
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT : 僅僅當系統提供的bar完全包含了劉海區時才允許window擴充到劉海區,否則window不會和劉海區重疊
第二種模式:
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES :允許window擴充到劉海區(原文說的是短邊的劉海區, 目前有劉海的手機都在短邊,是以就不糾結了)
第三種模式:
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER: 不允許window擴充到劉海區
我們可以設定是否允許window擴充到劉海區
WindowManager.LayoutParams lp =getWindow().getAttributes();
lp.layoutInDisplayCutoutMode
=WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
getWindow().setAttributes(lp);
例如一個有狀态欄的頁面, 我們可以這樣适配:
DisplayCutout cutout = getDisplayCutout();
if(cutout != null){
WindowManager.LayoutParams lp =getWindow().getAttributes();
lp.layoutInDisplayCutoutMode=WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
getWindow().setAttributes(lp);
}
如果覺得有幫助, 點個贊再走可好啊~