Android劉海屏涩拙、水滴屏全面屏適配方案

我將適配方案整理后荣茫,封裝成了一個(gè)庫(kù)并上傳至github,可參考使用

項(xiàng)目地址: https://github.com/smarxpan/NotchScreenTool

市面上的屏幕尺寸和全面屏方案五花八門(mén)输玷。

這里我使用了小米的圖來(lái)說(shuō)明:

image

上述兩種屏幕都可以統(tǒng)稱為劉海屏,不過(guò)對(duì)于右側(cè)較小的劉海靡馁,業(yè)界一般稱為水滴屏或美人尖欲鹏。為便于說(shuō)明,后文提到的「劉海屏」「劉海區(qū)」都同時(shí)指代上圖兩種屏幕臭墨。

當(dāng)我們?cè)谡勂聊贿m配時(shí)赔嚎,我們?cè)谡勈裁?/h2>
  1. 適應(yīng)更長(zhǎng)的屏幕
  2. 防止內(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ì)類似下面這樣:

image

黑色區(qū)域?yàn)槲蠢玫膮^(qū)域喘落。

適配方式

適配方式有兩種:

  1. 將targetSdkVersion版本設(shè)置到API 24及以上

    這個(gè)操作將會(huì)為<application> 標(biāo)簽隱式添加一個(gè)屬性,android:resizeableActivity="true", 該屬性的作用后面將詳細(xì)說(shuō)明斥黑。

  2. <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)始集乔,官方提供了適配異形屏的方式。

Support display cutouts

通過(guò)全新的 DisplayCutout 類坡椒,可以確定非功能區(qū)域的位置和形狀扰路,這些區(qū)域不應(yīng)顯示內(nèi)容。 要確定這些凹口屏幕區(qū)域是否存在及其位置倔叼,請(qǐng)使用 getDisplayCutout() 函數(shù)汗唱。

  1. 全新的窗口布局屬性 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

  2. 您可以按如下方法在任何運(yùn)行 Android P 的設(shè)備或模擬器上模擬屏幕缺口:

    1. 啟用開(kāi)發(fā)者選項(xiàng)。
    2. 在 Developer options 屏幕中,向下滾動(dòng)至 Drawing 部分并選擇 Simulate a display with a cutout识椰。
    3. 選擇凹口屏幕的大小绝葡。
  3. 適配參考:

     // 延伸顯示區(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è)備

方案一:

  1. 具體方式如下所示:

     <meta-data android:name="android.notch_support" android:value="true"/>
    
  2. 對(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>
    
  3. 對(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伊约,建議自行修改。

  1. 設(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");
         }
     }
    
  2. 清除添加的華為劉海屏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è)備

  1. 判斷是否是劉海屏

     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;
     }
    
  2. 設(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) {
         }
     }
    
  3. 獲取劉海寬高

     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è)備

  1. 判斷是否是劉海屏

     @Override
     public boolean hasNotch(Activity activity) {
         boolean ret = false;
         try {
             ret = activity.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
         } catch (Throwable ignore) {
         }
         return ret;
     }
    
  2. 獲取劉海的左上角和右下角的坐標(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)目中的代碼。

參考鏈接

聲明受限屏幕支持:聲明最大長(zhǎng)寬比

Android 8.1 兼容性定義

多窗口支持

Support display cutouts

華為劉海屏手機(jī)安卓O版本適配指導(dǎo)

OPPO凹形屏適配說(shuō)明

vivo 全面屏應(yīng)用適配指南

小米劉海屏水滴屏 Android O 適配

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末窍蓝,一起剝皮案震驚了整個(gè)濱河市腋颠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吓笙,老刑警劉巖淑玫,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡絮蒿,警方通過(guò)查閱死者的電腦和手機(jī)尊搬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)土涝,“玉大人佛寿,你說(shuō)我怎么就攤上這事〉常” “怎么了冀泻?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蜡饵。 經(jīng)常有香客問(wèn)我弹渔,道長(zhǎng),這世上最難降的妖魔是什么溯祸? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任肢专,我火速辦了婚禮,結(jié)果婚禮上您没,老公的妹妹穿的比我還像新娘鸟召。我一直安慰自己,他們只是感情好氨鹏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布欧募。 她就那樣靜靜地躺著盗飒,像睡著了一般赤拒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蛙婴,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天镣丑,我揣著相機(jī)與錄音舔糖,去河邊找鬼。 笑死莺匠,一個(gè)胖子當(dāng)著我的面吹牛金吗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播趣竣,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼摇庙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了遥缕?” 一聲冷哼從身側(cè)響起卫袒,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎单匣,沒(méi)想到半個(gè)月后夕凝,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體宝穗,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年码秉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逮矛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡泡徙,死狀恐怖橱鹏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情堪藐,我是刑警寧澤莉兰,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站礁竞,受9級(jí)特大地震影響糖荒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜模捂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一捶朵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧狂男,春花似錦综看、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至泡垃,卻和暖如春析珊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蔑穴。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工忠寻, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人存和。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓奕剃,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親捐腿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子纵朋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 前言目前市面上的劉海屏和水滴屏手機(jī)越來(lái)越多了,顏值方面是因人而異叙量,有的人覺(jué)得很好看,也有人覺(jué)得丑爆了九串,我個(gè)人覺(jué)得是...
    飄逸解構(gòu)閱讀 7,666評(píng)論 6 42
  • Apple一直在引領(lǐng)設(shè)計(jì)的潮流绞佩,自從 iPhone X 發(fā)布之后寺鸥,”劉海屏” 就一直存在爭(zhēng)議,本以為是一個(gè)美麗的錯(cuò)...
    AmberSiYing閱讀 1,171評(píng)論 0 0
  • 背景 劉海屏指的是手機(jī)屏幕正上方由于追求極致邊框而采用的一種手機(jī)解決方案品山。因形似劉海兒而得名胆建。也有一些其他叫法:挖...
    _九卿_閱讀 6,096評(píng)論 0 26
  • 我看那遠(yuǎn)方 那天空 藍(lán)底上 斜云蒼蒼 我看那遠(yuǎn)方 禿頭山 灰樹(shù)林 陽(yáng)光白亮 我看那遠(yuǎn)方 樹(shù)叢里 閃爍的 朦朧綠光 ...
    hi土豆先生閱讀 282評(píng)論 2 6
  • 上午坐車真直接去了東風(fēng),早尚且吃飯肘交,上午就沒(méi)有去試制車間笆载。一是今天不裝車,如果有問(wèn)題也就是前門(mén)多一個(gè)點(diǎn)光源涯呻,應(yīng)該不...
    陽(yáng)光下奔跑的孩子閱讀 316評(píng)論 0 0