本文由 玉剛說寫作平臺 提供寫作贊助
原作者:
四月葡萄
版權宣告:本文版權歸微信公眾號
玉剛說
所有,未經許可,不得以任何形式轉載
1.前言
先吐槽一下,劉海屏真醜。然而作為苦逼的開發者,還是要去適配劉海屏的。好了,吐槽完畢,進入正題。
這裡主要是介紹一下Android P中劉海屏的適配以及Android P之前的適配。為什麼要分開呢?因為Android P之前官方還沒提供API來進行適配,都是由各家廠商來提供適配方案的。2.Android P中的劉海屏適配
2.1 Google對劉海屏的支援介紹
Google將劉海屏命名為螢幕缺口了,這一小節內容摘自Android官方介紹: 螢幕缺口支援。
Android P 支援最新的全面屏以及為攝像頭和揚聲器預留空間的凹口螢幕。 通過全新的 DisplayCutout
類,可以確定非功能區域的位置和形狀,這些區域不應顯示內容。 要確定這些凹口螢幕區域是否存在及其位置,請使用 getDisplayCutout()
函式。
全新的視窗布局屬性 layoutInDisplayCutoutMode
讓您的應用可以為裝置凹口螢幕周圍的內容進行佈局。 您可以將此屬性設為下列值之一:
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
您可以按如下方法在任何執行 Android P 的裝置或模擬器上模擬螢幕缺口:
- 啟用開發者選項。
- 在 Developer options(開發者選項) 螢幕中,向下滾動至 Drawing(繪圖) 部分並選擇 Simulate a display with a cutout(模擬具有凹口的螢幕)。
- 選擇凹口螢幕的大小。
2.2 Android P提供提供的劉海屏適配方案
- 對於有狀態列的頁面,不會受到劉海屏特性的影響,因為劉海屏包含在狀態列中了;
- 全屏顯示的頁面,系統劉海屏方案會對應用介面做下移處理,避開劉海區顯示,這時會看到劉海區域變成一條黑邊,完全看不到劉海了;
- 已經適配Android P應用的全屏頁面可以通過谷歌提供的適配方案使用劉海區,真正做到全屏顯示。
2.3 Android P中支援的凹口螢幕型別
目前Android支援了三類凹口螢幕型別:邊角螢幕凹口(斜劉海)、雙螢幕凹口(劉海+鬍子)、長型螢幕凹口(劉海),如下圖所示:
目前的手機主要還是長型螢幕凹口,即劉海屏。其他斜劉海和鬍子手機應該還沒有實物吧?反正是亮瞎了狗眼了。
2.4 劉海屏佈局及安全區域說明
2.5 Android P中凹口螢幕相關介面
注意,以下介面都是要Build.VERSION.SDK_INT >= 28
才能呼叫到。
2.5.1 DisplayCutout類介面
主要用於獲取凹口位置和安全區域的位置等。主要介面如下所示:
方法 | 介面說明 |
---|---|
getBoundingRects() | 返回Rects的列表,每個Rects都是螢幕上非功能區域的邊界矩形。 |
getSafeInsetLeft () | 返回安全區域距離螢幕左邊的距離,單位是px。 |
getSafeInsetRight () | 返回安全區域距離螢幕右邊的距離,單位是px。 |
getSafeInsetTop () | 返回安全區域距離螢幕頂部的距離,單位是px。 |
getSafeInsetBottom() | 返回安全區域距離螢幕底部的距離,單位是px。 |
來看下例子。 這裡將開發者選項中的模擬具有凹口的螢幕選項改為雙螢幕凹口,即這裡應當有兩個劉海,然後,直接上程式碼:
public class NotchActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//開局就一張背景圖
setContentView(R.layout.notch);
getNotchParams();
}
@TargetApi(28)
public void getNotchParams() {
final View decorView = getWindow().getDecorView();
decorView.post(new Runnable() {
@Override
public void run() {
DisplayCutout displayCutout = decorView.getRootWindowInsets().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<Rect> 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);
}
}
}
});
}
}
複製程式碼
輸出結果為:
06-04 21:57:10.120 5698-5698/? E/TAG: 安全區域距離螢幕左邊的距離 SafeInsetLeft:0
06-04 21:57:10.120 5698-5698/? E/TAG: 安全區域距離螢幕右部的距離 SafeInsetRight:0
06-04 21:57:10.120 5698-5698/? E/TAG: 安全區域距離螢幕頂部的距離 SafeInsetTop:112
06-04 21:57:10.120 5698-5698/? E/TAG: 安全區域距離螢幕底部的距離 SafeInsetBottom:112
06-04 21:57:10.120 5698-5698/? E/TAG: 劉海屏數量:2
06-04 21:57:10.120 5698-5698/? E/TAG: 劉海屏區域:Rect(468, 0 - 972, 112)
06-04 21:57:10.120 5698-5698/? E/TAG: 劉海屏區域:Rect(468, 2448 - 972, 2560)
複製程式碼
可以看到,即距離頂部和底部各112px的區域就是安全區域了。
2.5.2 設定凹口螢幕顯示模式
使用例子:
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
getWindow().setAttributes(lp);
複製程式碼
Android P中新增了一個佈局引數屬性layoutInDisplayCutoutMode
,包含了三種不同的模式,如下所示:
模式 | 模式說明 |
---|---|
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT | 只有當DisplayCutout完全包含在系統欄中時,才允許視窗延伸到DisplayCutout區域。 否則,視窗布局不與DisplayCutout區域重疊。 |
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER | 該視窗決不允許與DisplayCutout區域重疊。 |
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES | 該視窗始終允許延伸到螢幕短邊上的DisplayCutout區域。 |
下面我們來寫個Demo看下這三種模式的顯示效果: Demo很簡單,就是顯示一張背景圖,相關背景佈局就不貼了,來看下主要的程式碼:
public class NotchActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//去掉標題
requestWindowFeature(Window.FEATURE_NO_TITLE);
//開局就一張背景圖
setContentView(R.layout.notch);
//全屏顯示
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
WindowManager.LayoutParams lp = getWindow().getAttributes();
//下面圖1
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
//下面圖2
// lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
//下面圖3
// lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
getWindow().setAttributes(lp);
}
}
複製程式碼
這裡設定為全屏的顯示效果,三種模式的結果如下圖所示:
圖一可以看到上面有黑邊。 圖二明顯看到有劉海。 圖三同樣是黑邊。可以看到:
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
模式會讓螢幕到延申劉海區域中。LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
模式不會讓螢幕到延申劉海區域中,會留出一片黑色區域。LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
模式在全屏顯示下跟LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
一樣。
我們再來看看LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
模式在沉浸式狀態列下的效果,程式碼如下:
public class NotchActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//去掉標題
requestWindowFeature(Window.FEATURE_NO_TITLE);
//開局就一張背景圖
setContentView(R.layout.notch);
//全屏顯示
// getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
//沉浸式狀態列
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
getWindow().setAttributes(lp);
}
}
複製程式碼
如下圖所示:
可以看到:
當劉海區域完全在系統的狀態列時,
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
的顯示效果與LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
一致。
所以,當我們進行劉海屏的適配時,請根據實際情況去使用不同的layoutInDisplayCutoutMode
。
2.6 那麼劉海屏該如何適配呢?
2.6.1 如果頁面存在狀態列
- 那麼很簡單,不用適配,因為劉海區域會包含在狀態列中了。
- 如果不想看到劉海區域,可以使用
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
將劉海區域變成一條黑色邊。
2.6.2 如果頁面是全屏顯示
- 不適配的話將會留出一條黑色邊。
- 要做到真正全屏的話,那麼就先要獲取到劉海的區域(危險區域),內容部分(操作按鈕等)應當避開危險區域,保證在安全區域中展示。橫屏的話兩邊都需要注意避開劉海(危險區域)。
3.Android P之前的劉海屏適配
上面是Android P才有的解決方案,在P之前呢,上面的程式碼通通都沒用。然而我們偉大的國產廠商在Android P之前(基本都是Android O)就用上了高檔大氣上檔次的劉海屏,所以,這也造就了各大廠商在Android P之前的解決方案百花齊放。下面,我們來看下主流廠商:華為、vivo、OPPO、小米等所提供的方案。
注:相關的程式碼都已封裝好,可以直接拷貝使用。
3.1 華為
3.1.1 使用劉海區顯示
使用新增的meta-data
屬性android.notch_support
。
在應用的AndroidManifest.xml
中增加meta-data
屬性,此屬性不僅可以針對Application
生效,也可以對Activity
配置生效。
如下所示:
<meta-data android:name="android.notch_support" android:value="true"/>
複製程式碼
- 對
Application
生效,意味著該應用的所有頁面,系統都不會做豎屏場景的特殊下移或者是橫屏場景的右移特殊處理。 - 對
Activity
生效,意味著可以針對單個頁面進行劉海屏適配,設定了該屬性的Activity
系統將不會做特殊處理。
實際上還有一種程式碼實現的方式,不過程式碼比較多,這裡就不貼了,有興趣的話可以在文末的連結中點進去看看。
3.1.2 是否有劉海屏
通過以下程式碼即可知道華為手機上是否有劉海屏了,true
為有劉海,false
則沒有。
public static boolean hasNotchAtHuawei(Context context) {
boolean ret = false;
try {
ClassLoader classLoader = context.getClassLoader();
Class HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil");
Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen");
ret = (boolean) get.invoke(HwNotchSizeUtil);
} catch (ClassNotFoundException e) {
Log.e("Notch", "hasNotchAtHuawei ClassNotFoundException");
} catch (NoSuchMethodException e) {
Log.e("Notch", "hasNotchAtHuawei NoSuchMethodException");
} catch (Exception e) {
Log.e("Notch", "hasNotchAtHuawei Exception");
} finally {
return ret;
}
}
複製程式碼
3.1.3 劉海尺寸
華為提供了介面獲取劉海的尺寸,如下:
//獲取劉海尺寸:width、height
//int[0]值為劉海寬度 int[1]值為劉海高度
public static int[] getNotchSizeAtHuawei(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("Notch", "getNotchSizeAtHuawei ClassNotFoundException");
} catch (NoSuchMethodException e) {
Log.e("Notch", "getNotchSizeAtHuawei NoSuchMethodException");
} catch (Exception e) {
Log.e("Notch", "getNotchSizeAtHuawei Exception");
} finally {
return ret;
}
}
複製程式碼
3.2 vivo
vivo在設定--顯示與亮度--第三方應用顯示比例中可以切換是否全屏顯示還是安全區域顯示。
3.2.1 是否有劉海屏
public static final int VIVO_NOTCH = 0x00000020;//是否有劉海
public static final int VIVO_FILLET = 0x00000008;//是否有圓角
public static boolean hasNotchAtVoio(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", "hasNotchAtVoio ClassNotFoundException");
} catch (NoSuchMethodException e) {
Log.e("Notch", "hasNotchAtVoio NoSuchMethodException");
} catch (Exception e) {
Log.e("Notch", "hasNotchAtVoio Exception");
} finally {
return ret;
}
}
複製程式碼
3.2.2 劉海尺寸
vivo不提供介面獲取劉海尺寸,目前vivo的劉海寬為100dp,高為27dp。
3.3 OPPO
OPPO目前在設定 -- 顯示 -- 應用全屏顯示 -- 凹形區域顯示控制,裡面有關閉凹形區域開關。
3.3.1 是否有劉海屏
public static boolean hasNotchInScreenAtOPPO(Context context) {
return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
}
複製程式碼
3.3.2 劉海尺寸
OPPO不提供介面獲取劉海尺寸,目前其有劉海屏的機型尺寸規格都是統一的。不排除以後機型會有變化。 其螢幕寬度為1080px,高度為2280px。劉海區域則都是寬度為324px, 高度為80px。
3.4 小米
3.4.1 是否有劉海屏
系統增加了 property ro.miui.notch
,值為1時則是 Notch 屏手機。
手頭上沒有小米8的手機,暫時沒法驗證,這裡就不貼程式碼了,免得誤導大家。後面測試過再放出來。
3.4.2 劉海尺寸
小米的狀態列高度會略高於劉海屏的高度,因此可以通過獲取狀態列的高度來間接避開劉海屏,獲取狀態列的高度程式碼如下:
public static 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;
}
複製程式碼
其他手機也可以通過這個方法來間接避開劉海屏,但是有可能有些手機的劉海屏高度會高於狀態列的高度,所以這個方法獲取到的結果並不一定安全。
3.5 其他廠商
如果要適配其他廠商的劉海屏,可以去找下他們的開發者文件,一般都會有提供的,這裡就不詳述了。