背景
自從iphone x發(fā)布后,各大廠商也發(fā)布了類似的劉海屏手機(jī)(“頂部屏幕凹槽設(shè)計(jì)”),開發(fā)者應(yīng)該如何適配呢?
原理
為什么會(huì)有劉海屏?
因?yàn)榇蠹矣凶耘牡男枨?,需要攝像頭前置,除了攝像頭前置外,劉海屏上還有一些其他的傳感器,所以不同廠商的劉海屏長度也不相同。
這里主要是介紹一下Android P發(fā)布之后劉海屏的適配以及Android P之前的適配。為什么要分開呢?因?yàn)锳ndroid P之前官方還沒提供API來進(jìn)行適配,都是由各家廠商來提供適配方案的。
2.6 那么劉海屏該如何適配呢?
2.6.1 如果頁面存在狀態(tài)欄
[if !supportLists]·?[endif]那么很簡單,不用適配,因?yàn)閯⒑^(qū)域會(huì)包含在狀態(tài)欄中了。
[if !supportLists]·?[endif]如果不想看到劉海區(qū)域,可以使用LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER將劉海區(qū)域變成一條黑色邊。
2.6.2 如果頁面是全屏顯示
[if !supportLists]·?[endif]不適配的話將會(huì)留出一條黑色邊。
[if !supportLists]·?[endif]要做到真正全屏的話,那么就先要獲取到劉海的區(qū)域(危險(xiǎn)區(qū)域),內(nèi)容部分(操作按鈕等)應(yīng)當(dāng)避開危險(xiǎn)區(qū)域,保證在安全區(qū)域中展示。橫屏的話兩邊都需要注意避開劉海(危險(xiǎn)區(qū)域)。
華為適配劉海屏主要有以下幾個(gè)步驟:?1.配置meta-data?
[if !supportLists]2.?[endif]檢測是否存在劉海屏3.獲取劉海屏的參數(shù)4. UI適配
?
?
?
?
1.配置meta-data?華為新增的Meta-data屬性android.notch_support在應(yīng)用的AndroidManifest.xml中增加meta-data屬性,此屬性不僅可以針對(duì)Application生效,也可以對(duì)Activity配置生效,具體方式如下所示:
[if !supportLists]·?[endif]1
①對(duì)Application生效,意味著該應(yīng)用的所有頁面,(會(huì)對(duì)你App的所有頁面都進(jìn)行適配)系統(tǒng)都不會(huì)做豎屏場景的特殊下移或者是橫屏場景的右移特殊處理:
?② 對(duì)Activity生效,意味著可以針對(duì)單個(gè)頁面進(jìn)行劉海屏適配,設(shè)置了該屬性的Activity系統(tǒng)將不會(huì)做特殊處理:?


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.獲取劉海屏的參數(shù)
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),應(yīng)用在華為劉海屏手機(jī)上就能夠默認(rèn)使用劉海區(qū)顯示了,但是為了避免出現(xiàn)UI被劉海區(qū)遮擋的問題,還是需要應(yīng)用自己做一些額外的UI適配工作:(1)判斷是否劉海屏,通過華為劉海屏SDK的API判斷,具體參考3.2.1章節(jié)(2)如果是劉海屏手機(jī)需要應(yīng)用自己調(diào)整布局避開劉海區(qū),布局原則:保證重要的文字、圖片和視頻信息、可點(diǎn)擊的控件和圖標(biāo)還有應(yīng)用彈窗等等布局建議顯示在狀態(tài)欄區(qū)域以下(安全區(qū)域);不重要,遮擋不會(huì)出現(xiàn)問題的布局可以延伸到狀態(tài)欄區(qū)域(危險(xiǎn)區(qū)域)顯示,按照這種布局原則修改,可以一次修改就能適配所有的劉海屏手機(jī):

獲取系統(tǒng)狀態(tài)欄高度接口:
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
vivo 和 OPPO官網(wǎng)僅僅給出了適配指導(dǎo),沒有給出具體方案,簡單總結(jié)為:?如有是具有劉海屏的手機(jī),豎屏顯示狀態(tài)欄,橫屏不要在危險(xiǎn)區(qū)顯示重要信息或者設(shè)置點(diǎn)擊事件。
首先,判斷是不是劉海屏手機(jī)。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; }
}
然后在進(jìn)行適配,官方這方面的資料很少也不是很詳細(xì)
?
google官方
google從Android P開始為劉海屏提供支持,目前提供了一個(gè)類和三種模式:?一個(gè)類指的是可以用DisplayCutout這個(gè)類找出劉海(cutout)的位置和形狀,調(diào)用getDisplayCutout()這個(gè)方法可以獲取劉海(cut
out)的位置和區(qū)域。
3.1 開啟劉海屏
我們在全屏的頁面,需要單獨(dú)開啟支持劉海屏。而Google 提供的適配方案,可以設(shè)置是否在全屏模式下,使用劉海屏的區(qū)域。
WindowManager.LayoutParams lp
????????????????=getWindow().getAttributes();
lp.layoutInDisplayCutoutMode =
????????????????WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
getWindow().setAttributes(lp);
新的布局屬性layoutInDisplayCutoutMode?包含三種可選的模式,
模式模式說明?
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT只有當(dāng)DisplayCutout完全包含在系統(tǒng)欄中時(shí),才允許窗口延伸到DisplayCutout區(qū)域。 否則,窗口布局不與DisplayCutout區(qū)域重疊。?
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER該窗口決不允許與DisplayCutout區(qū)域重疊。?
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES該窗口始終允許延伸到屏幕短邊上的DisplayCutout區(qū)域。?
[if !supportLists]1.?[endif]LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES模式會(huì)讓屏幕到延申劉海區(qū)域中。
[if !supportLists]2.?[endif]LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER模式不會(huì)讓屏幕到延申劉海區(qū)域中,會(huì)留出一片黑色區(qū)域。
[if !supportLists]3.?[endif]LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT模式在全屏顯示下跟LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER一樣。
這里設(shè)置為全屏的顯示效果,三種模式的結(jié)果如下圖所示
[if !supportLists]1.?[endif]LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES模式會(huì)讓屏幕到延申劉海區(qū)域中。
[if !supportLists]2.?[endif]LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER模式不會(huì)讓屏幕到延申劉海區(qū)域中,會(huì)留出一片黑色區(qū)域。
[if !supportLists]3.?[endif]LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT模式在全屏顯示下跟LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER一樣。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT模式在沉浸式狀態(tài)欄下的效果:
3.2 劉海屏的高度
在全屏模式下,我們需要有辦法獲取到劉海屏凹槽的高度,才可以做到設(shè)計(jì)和布局的時(shí)候,留出安全距離。
雖然Google 要求,劉海屏的凹槽,必須和劉海的高度保持一致,而劉海屏又被隱藏在狀態(tài)欄了,所以有一個(gè)思路是直接獲取狀態(tài)欄的高度,來判斷劉海之外,可布局的安全區(qū)域。
不過Android P 已經(jīng)預(yù)留出了標(biāo)準(zhǔn)的測量 劉海屏凹槽 的 Api:DisplayCutout。
劉海屏的凹槽,就在屏幕的中間,所以只有g(shù)etSafeInsetTop()?方法返回的結(jié)果
: