作者:王英豪
連結: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、資料庫、運維等。