我將適配方案整理后荣茫,封裝成了一個(gè)庫(kù)并上傳至github,可參考使用
項(xiàng)目地址: https://github.com/smarxpan/NotchScreenTool
市面上的屏幕尺寸和全面屏方案五花八門(mén)输玷。
這里我使用了小米的圖來(lái)說(shuō)明:
上述兩種屏幕都可以統(tǒng)稱為劉海屏,不過(guò)對(duì)于右側(cè)較小的劉海靡馁,業(yè)界一般稱為水滴屏或美人尖欲鹏。為便于說(shuō)明,后文提到的「劉海屏」「劉海區(qū)」都同時(shí)指代上圖兩種屏幕臭墨。
當(dāng)我們?cè)谡勂聊贿m配時(shí)赔嚎,我們?cè)谡勈裁?/h2>
- 適應(yīng)更長(zhǎng)的屏幕
- 防止內(nèi)容被劉海遮擋
其中第一點(diǎn)是所有應(yīng)用都需要適配的,對(duì)應(yīng)下文的聲明最大長(zhǎng)寬比
而第二點(diǎn),如果應(yīng)用本身不需要全屏顯示或使用沉浸式狀態(tài)欄尤误,是不需要適配的侠畔。
針對(duì)需要適配第二點(diǎn)的應(yīng)用,需要獲取劉海的位置和寬高损晤,然后將顯示內(nèi)容避開(kāi)即可软棺。
聲明最大長(zhǎng)寬比
以前的普通屏長(zhǎng)寬比為16:9,全面屏手機(jī)的屏幕長(zhǎng)寬比增大了很多尤勋,如果不適配的話就會(huì)類似下面這樣:
黑色區(qū)域?yàn)槲蠢玫膮^(qū)域喘落。
適配方式
適配方式有兩種:
-
將targetSdkVersion版本設(shè)置到API 24及以上
這個(gè)操作將會(huì)為
<application>
標(biāo)簽隱式添加一個(gè)屬性,android:resizeableActivity="true"
, 該屬性的作用后面將詳細(xì)說(shuō)明斥黑。 -
在
<application>
標(biāo)簽中增加屬性:android:resizeableActivity="false"
同時(shí)在節(jié)點(diǎn)下增加一個(gè)
meta-data
標(biāo)簽:<!-- 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" />
原理說(shuō)明
這里涉及到的知識(shí)點(diǎn)是android:resizeableActivity屬性揖盘。
在 Android 7.0(API 級(jí)別 24)或更高版本的應(yīng)用,android:resizeableActivity屬性默認(rèn)為true(對(duì)應(yīng)適配方式1)锌奴。這個(gè)屬性是控制多窗口顯示的兽狭,決定當(dāng)前的應(yīng)用或者Activity是否支持多窗口。
在清單的<activity>
或 <application>
節(jié)點(diǎn)中設(shè)置該屬性鹿蜀,啟用或禁用多窗口顯示:
android:resizeableActivity=["true" | "false"]
如果該屬性設(shè)置為 true箕慧,Activity 將能以分屏和自由形狀模式啟動(dòng)。 如果此屬性設(shè)置為 false茴恰,Activity 將不支持多窗口模式颠焦。 如果該值為 false,且用戶嘗試在多窗口模式下啟動(dòng) Activity往枣,該 Activity 將全屏顯示伐庭。
適配方式2即為設(shè)置屏幕的最大長(zhǎng)寬比,這是官方提供的設(shè)置方式分冈。
如果設(shè)置了最大長(zhǎng)寬比圾另,必須android:resizeableActivity="false"
。 否則最大長(zhǎng)寬比沒(méi)有任何作用雕沉。
適配劉海屏
Android9.0及以上適配
Android P(9.0)開(kāi)始集乔,官方提供了適配異形屏的方式。
通過(guò)全新的 DisplayCutout 類坡椒,可以確定非功能區(qū)域的位置和形狀扰路,這些區(qū)域不應(yīng)顯示內(nèi)容。 要確定這些凹口屏幕區(qū)域是否存在及其位置倔叼,請(qǐng)使用 getDisplayCutout() 函數(shù)汗唱。
-
全新的窗口布局屬性 layoutInDisplayCutoutMode 讓您的應(yīng)用可以為設(shè)備凹口屏幕周圍的內(nèi)容進(jìn)行布局。 您可以將此屬性設(shè)為下列值之一:
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
默認(rèn)值是
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
丈攒,劉海區(qū)域不會(huì)顯示內(nèi)容渡嚣,需要將值設(shè)置為LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
-
您可以按如下方法在任何運(yùn)行 Android P 的設(shè)備或模擬器上模擬屏幕缺口:
- 啟用開(kāi)發(fā)者選項(xiàng)。
- 在 Developer options 屏幕中,向下滾動(dòng)至 Drawing 部分并選擇 Simulate a display with a cutout识椰。
- 選擇凹口屏幕的大小绝葡。
-
適配參考:
// 延伸顯示區(qū)域到劉海 WindowManager.LayoutParams lp = window.getAttributes(); lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; window.setAttributes(lp); // 設(shè)置頁(yè)面全屏顯示 final View decorView = window.getDecorView(); decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
其中延伸顯示區(qū)域到劉海的代碼,也可以通過(guò)修改Activity或應(yīng)用的style實(shí)現(xiàn)腹鹉,例如:
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="AppTheme" parent="xxx"> <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> </style> </resources>
Android O 適配
因Google官方的適配方案到Android P才推出藏畅,因此在Android O設(shè)備上,各家廠商有自己的實(shí)現(xiàn)方案功咒。
我這里主要適配了華為愉阎、小米、oppo力奋,這三家都給了完整的解決方案榜旦。至于vivo,vivo給了判斷是否劉海屏的API景殷,但是沒(méi)用設(shè)置劉海區(qū)域顯示到API溅呢,因此無(wú)需適配。
適配華為Android O設(shè)備
方案一:
-
具體方式如下所示:
<meta-data android:name="android.notch_support" android:value="true"/>
-
對(duì)Application生效猿挚,意味著該應(yīng)用的所有頁(yè)面咐旧,系統(tǒng)都不會(huì)做豎屏場(chǎng)景的特殊下移或者是橫屏場(chǎng)景的右移特殊處理:
<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>
-
對(duì)Activity生效,意味著可以針對(duì)單個(gè)頁(yè)面進(jìn)行劉海屏適配绩蜻,設(shè)置了該屬性的Activity系統(tǒng)將不會(huì)做特殊處理:
<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>
方案二
對(duì)Application生效铣墨,意味著該應(yīng)用的所有頁(yè)面,系統(tǒng)都不會(huì)做豎屏場(chǎng)景的特殊下移或者是橫屏場(chǎng)景的右移特殊處理
我的NotchScreenTool中使用的就是方案二办绝,如果需要針對(duì)Activity伊约,建議自行修改。
-
設(shè)置應(yīng)用窗口在華為劉海屏手機(jī)使用劉海區(qū)
/*劉海屏全屏顯示FLAG*/ public static final int FLAG_NOTCH_SUPPORT=0x00010000; /** * 設(shè)置應(yīng)用窗口在華為劉海屏手機(jī)使用劉海區(qū) * @param window 應(yīng)用頁(yè)面window對(duì)象 */ 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孕蝉,恢復(fù)應(yīng)用不使用劉海區(qū)顯示
/** * 設(shè)置應(yīng)用窗口在華為劉海屏手機(jī)使用劉海區(qū) * @param window 應(yīng)用頁(yè)面window對(duì)象 */ 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"); } }
適配小米Android O設(shè)備
-
判斷是否是劉海屏
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; }
-
設(shè)置顯示到劉海區(qū)域
@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; }
適配oppoAndroid O設(shè)備
-
判斷是否是劉海屏
@Override public boolean hasNotch(Activity activity) { boolean ret = false; try { ret = activity.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism"); } catch (Throwable ignore) { } return ret; }
-
獲取劉海的左上角和右下角的坐標(biāo)
/** * 獲取劉海的坐標(biāo) * <p> * 屬性形如:[ro.oppo.screen.heteromorphism]: [378,0:702,80] * <p> * 獲取到的值為378,0:702,80 * <p> * <p> * (378,0)是劉海區(qū)域左上角的坐標(biāo) * <p> * (702,80)是劉海區(qū)域右下角的坐標(biāo) */ 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機(jī)型不需要設(shè)置顯示到劉海區(qū)域碱妆,只要設(shè)置了應(yīng)用全屏就會(huì)默認(rèn)顯示。
因此Oppo機(jī)型必須適配昔驱。
適配總結(jié)
根據(jù)上述功能,我將其整理成了一個(gè)依賴庫(kù):NotchScreenTool
使用起來(lái)很簡(jiǎn)單:
// 支持顯示到劉海區(qū)域
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());
}
}
}
});
獲取劉海區(qū)域信息后就可以根據(jù)自己應(yīng)用的需要上忍,來(lái)避開(kāi)重要的控件骤肛。
詳情可參考我項(xiàng)目中的代碼。