詳解Android劉海屏適配
Apple一直在引領設計的潮流,自從 iPhone X 釋出之後,”劉海屏” 就一直存在爭議,本以為是一個美麗的錯誤(Bug),卻早就了一時間“劉海屏”的模仿潮。目前,國內已經推出的劉海屏”手機有 OPPO R15 和 華為 P20,並且Google也在IO大會上提高了相應的適配方案。
什麼是劉海屏
螢幕的正上方居中位置(下圖黑色區域)會被挖掉一個孔,螢幕被挖掉的區域無法正常顯示內容,這種型別的螢幕就是劉海屏,也有其他叫法:挖孔屏、凹凸屏等等,這裡統一按劉海屏命名。
就現在市場上的情況來說,“劉海屏”主要分成兩類,一類是標準的 Android P Api,另外一類就是廠商在 Android P 以下的系統,做的特殊適配。
例如:華為 P20 就是採用的 Android P 標準 Api 的方式,而 OPPO R15 就不一樣了,它有自己的適配 Api。
如何適配劉海屏
由於Android p正式版前兩天才釋出, 當前市面上的Android 劉海屏手機還不能用Android 官方提供的方案來解決,那怎麼辦呢?還好幾個廠商自己給出了適配方案(文末會接受使用Android P來適配劉海屏)。
華為P20
華為給出的文件最為詳細適配文件,P20 pro預裝系統對未做劉海屏適配處理的app有一定處理,處理的邏輯如下圖。
右上圖可知,華為系統做偏移處理的有以下2種情況:
1.未設定meta-data值,頁面橫屏狀態
2.未設定meta-data值,頁面豎屏狀態,不顯示狀態列
適配劉海屏主要有以下幾個步驟:
1.配置meta-data
華為新增的Meta-data屬性android.notch_support在應用的AndroidManifest.xml中增加meta-data屬性,此屬性不僅可以針對Application生效,也可以對Activity配置生效,具體方式如下所示:
<meta-data android:name="android.notch_support" android:value="true"/>
①對Application生效,意味著該應用的所有頁面,系統都不會做豎屏場景的特殊下移或者是橫屏場景的右移特殊處理:
② 對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適配
通過增加上面適配方案提到的配置(meta-data或者是Flag),應用在華為劉海屏手機上就能夠預設使用劉海區顯示了,但是為了避免出現UI被劉海區遮擋的問題,還是需要應用自己做一些額外的UI適配工作:
(1)判斷是否劉海屏,通過華為劉海屏SDK的API判斷,具體參考3.2.1章節
(2)如果是劉海屏手機需要應用自己調整佈局避開劉海區,佈局原則:保證重要的文字、圖片和視訊資訊、可點選的控制元件和圖示還有應用彈窗等等佈局建議顯示在狀態列區域以下(安全區域);不重要,遮擋不會出現問題的佈局可以延伸到狀態列區域(危險區域)顯示,按照這種佈局原則修改,可以一次修改就能適配所有的劉海屏手機:
獲取系統狀態列高度介面:
public static int getStatusBarHeight(Context context) {
int result = 0;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
return result;
}
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; }
}
然後在進行適配,官方這方面的資料很少也不是很詳細
google官方
google從Android P開始為劉海屏提供支援,目前提供了一個類和三種模式:
一個類指的是可以用DisplayCutout這個類找出劉海(cutout)的位置和形狀,呼叫getDisplayCutout()這個方法可以獲取劉海(cutout)的位置和區域。例如:
DisplayCutout cutout = mContext.getDisplayCutout();
Google官方提供了三種模式,分別是:
- 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);
}
當然如果你身邊還沒有劉海屏手機,可以使用Android P提供的模擬器,更新SDK到Android P preview版本。
然後,可以通過開發者選項裡的 “Simulate a display with a cutout”,開啟劉海屏的支援,並且劉海屏有多個版本,需要注意它們的區別。