我將適配方案整理後,封裝成了一個庫並上傳至github,可參考使用html
項目地址: github.com/smarxpan/No…android
市面上的屏幕尺寸和全面屏方案五花八門。git
這裏我使用了小米的圖來講明:github
上述兩種屏幕均可以統稱爲劉海屏,不過對於右側較小的劉海,業界通常稱爲水滴屏或美人尖。爲便於說明,後文提到的「劉海屏」「劉海區」都同時指代上圖兩種屏幕。c#
其中第一點是全部應用都須要適配的,對應下文的聲明最大長寬比
api
而第二點,若是應用自己不須要全屏顯示或使用沉浸式狀態欄,是不須要適配的。app
針對須要適配第二點的應用,須要獲取劉海的位置和寬高,而後將顯示內容避開便可。ide
之前的普通屏長寬比爲16:9,全面屏手機的屏幕長寬比增大了不少,若是不適配的話就會相似下面這樣:函數
黑色區域爲未利用的區域。佈局
適配方式有兩種:
將targetSdkVersion版本設置到API 24及以上
這個操做將會爲<application>
標籤隱式添加一個屬性,android:resizeableActivity="true"
, 該屬性的做用後面將詳細說明。
在 <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"
。 不然最大長寬比沒有任何做用。
Android P(9.0)開始,官方提供了適配異形屏的方式。
經過全新的 DisplayCutout 類,能夠肯定非功能區域的位置和形狀,這些區域不該顯示內容。 要肯定這些凹口屏幕區域是否存在及其位置,請使用 getDisplayCutout() 函數。
全新的窗口布局屬性 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
您能夠按以下方法在任何運行 Android P 的設備或模擬器上模擬屏幕缺口:
適配參考:
// 延伸顯示區域到劉海
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>
複製代碼
因Google官方的適配方案到Android P才推出,所以在Android O設備上,各家廠商有本身的實現方案。
我這裏主要適配了華爲、小米、oppo,這三家都給了完整的解決方案。至於vivo,vivo給了判斷是否劉海屏的API,可是沒用設置劉海區域顯示到API,所以無需適配。
方案一:
具體方式以下所示:
<meta-data android:name="android.notch_support" android:value="true"/>
複製代碼
對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>
複製代碼
對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,建議自行修改。
設置應用窗口在華爲劉海屏手機使用劉海區
/*劉海屏全屏顯示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");
}
}
複製代碼
清除添加的華爲劉海屏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");
}
}
複製代碼
判斷是不是劉海屏
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;
}
複製代碼
設置顯示到劉海區域
@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) {
}
}
複製代碼
獲取劉海寬高
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;
}
複製代碼
判斷是不是劉海屏
@Override
public boolean hasNotch(Activity activity) {
boolean ret = false;
try {
ret = activity.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
} catch (Throwable ignore) {
}
return ret;
}
複製代碼
獲取劉海的左上角和右下角的座標
/**
* 獲取劉海的座標
* <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());
}
}
}
});
複製代碼
獲取劉海區域信息後就能夠根據本身應用的須要,來避開重要的控件。
詳情可參考我項目中的代碼。