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

我將適配方案整理后北启,封裝成了一個庫并上傳至github卜朗,可參考使用
項目地址:https://github.com/smarxpan/NotchScreenTool

作者:聰明叉
鏈接:http://www.reibang.com/p/2b8db60ba8df

市面上的屏幕尺寸和全面屏方案五花八門。

這里我使用了小米的圖來說明:

上述兩種屏幕都可以統(tǒng)稱為劉海屏咕村,不過對于右側(cè)較小的劉海场钉,業(yè)界一般稱為水滴屏或美人尖。為便于說明懈涛,后文提到的「劉海屏」「劉海區(qū)」都同時指代上圖兩種屏幕。

當我們在談屏幕適配時批钠,我們在談什么

  1. 適應(yīng)更長的屏幕
  2. 防止內(nèi)容被劉海遮擋

其中第一點是所有應(yīng)用都需要適配的宇植,對應(yīng)下文的聲明最大長寬比

而第二點,如果應(yīng)用本身不需要全屏顯示或使用沉浸式狀態(tài)欄埋心,是不需要適配的指郁。

針對需要適配第二點的應(yīng)用,需要獲取劉海的位置和寬高拷呆,然后將顯示內(nèi)容避開即可闲坎。

聲明最大長寬比

以前的普通屏長寬比為16:9,全面屏手機的屏幕長寬比增大了很多,如果不適配的話就會類似下面這樣:

黑色區(qū)域為未利用的區(qū)域箫柳。

適配方式

適配方式有兩種:

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

    這個操作將會為<application> 標簽隱式添加一個屬性手形,android:resizeableActivity="true", 該屬性的作用后面將詳細說明。

  2. <application> 標簽中增加屬性:android:resizeableActivity="false"

    同時在節(jié)點下增加一個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)或更高版本的應(yīng)用库糠,android:resizeableActivity屬性默認為true(對應(yīng)適配方式1)。這個屬性是控制多窗口顯示的涮毫,決定當前的應(yīng)用或者Activity是否支持多窗口瞬欧。

多窗口支持

在清單的<activity><application>節(jié)點中設(shè)置該屬性,啟用或禁用多窗口顯示:

android:resizeableActivity=["true" | "false"]

如果該屬性設(shè)置為 true罢防,Activity 將能以分屏和自由形狀模式啟動艘虎。 如果此屬性設(shè)置為 false,Activity 將不支持多窗口模式咒吐。 如果該值為 false野建,且用戶嘗試在多窗口模式下啟動 Activity,該 Activity 將全屏顯示恬叹。

適配方式2即為設(shè)置屏幕的最大長寬比候生,這是官方提供的設(shè)置方式。

如果設(shè)置了最大長寬比绽昼,必須android:resizeableActivity="false"唯鸭。 否則最大長寬比沒有任何作用。

適配劉海屏

Android9.0及以上適配

Android P(9.0)開始硅确,官方提供了適配異形屏的方式目溉。

Support display cutouts

通過全新的 DisplayCutout 類,可以確定非功能區(qū)域的位置和形狀菱农,這些區(qū)域不應(yīng)顯示內(nèi)容缭付。 要確定這些凹口屏幕區(qū)域是否存在及其位置,請使用 getDisplayCutout() 函數(shù)大莫。

  1. 全新的窗口布局屬性 layoutInDisplayCutoutMode 讓您的應(yīng)用可以為設(shè)備凹口屏幕周圍的內(nèi)容進行布局蛉腌。 您可以將此屬性設(shè)為下列值之一:

    • 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,劉海區(qū)域不會顯示內(nèi)容只厘,需要將值設(shè)置為LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES

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

    1. 啟用開發(fā)者選項烙丛。
    2. 在 Developer options 屏幕中,向下滾動至 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è)置頁面全屏顯示
     final View decorView = window.getDecorView();
     decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
    
    

    其中延伸顯示區(qū)域到劉海的代碼,也可以通過修改Activity或應(yīng)用的style實現(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è)備上飒房,各家廠商有自己的實現(xiàn)方案。

我這里主要適配了華為媚值、小米狠毯、oppo,這三家都給了完整的解決方案褥芒。至于vivo嚼松,vivo給了判斷是否劉海屏的API,但是沒用設(shè)置劉海區(qū)域顯示到API锰扶,因此無需適配献酗。

適配華為Android O設(shè)備

方案一:

  1. 具體方式如下所示:

     <meta-data android:name="android.notch_support" android:value="true"/>
    
    
  2. 對Application生效,意味著該應(yīng)用的所有頁面坷牛,系統(tǒ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. 對Activity生效罕偎,意味著可以針對單個頁面進行劉海屏適配,設(shè)置了該屬性的Activity系統(tǒ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">
         <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生效京闰,意味著該應(yīng)用的所有頁面颜及,系統(tǒng)都不會做豎屏場景的特殊下移或者是橫屏場景的右移特殊處理

我的NotchScreenTool中使用的就是方案二,如果需要針對Activity忙干,建議自行修改器予。

  1. 設(shè)置應(yīng)用窗口在華為劉海屏手機使用劉海區(qū)

     /*劉海屏全屏顯示FLAG*/
     public static final int FLAG_NOTCH_SUPPORT=0x00010000;
     /**
      * 設(shè)置應(yīng)用窗口在華為劉海屏手機使用劉海區(qū)
      * @param window 應(yīng)用頁面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");
         }
     }
    
    
  2. 清除添加的華為劉海屏Flag浪藻,恢復(fù)應(yīng)用不使用劉海區(qū)顯示

     /**
      * 設(shè)置應(yīng)用窗口在華為劉海屏手機使用劉海區(qū)
      * @param window 應(yīng)用頁面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");
         }
     }
    
    

適配小米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. 獲取劉海的左上角和右下角的坐標

     /**
      * 獲取劉海的坐標
      * <p>
      * 屬性形如:[ro.oppo.screen.heteromorphism]: [378,0:702,80]
      * <p>
      * 獲取到的值為378,0:702,80
      * <p>
      * <p>
      * (378,0)是劉海區(qū)域左上角的坐標
      * <p>
      * (702,80)是劉海區(qū)域右下角的坐標
      */
     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機型不需要設(shè)置顯示到劉海區(qū)域,只要設(shè)置了應(yīng)用全屏就會默認顯示。

因此Oppo機型必須適配乾蓬。

適配總結(jié)

根據(jù)上述功能纸俭,我將其整理成了一個依賴庫:NotchScreenTool

使用起來很簡單:

// 支持顯示到劉海區(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)用的需要,來避開重要的控件萌丈。

詳情可參考我項目中的代碼赞哗。

參考資料

聲明受限屏幕支持:聲明最大長寬比

Android 8.1 兼容性定義

多窗口支持

Support display cutouts

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

OPPO凹形屏適配說明

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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市辆雾,隨后出現(xiàn)的幾起案子肪笋,更是在濱河造成了極大的恐慌,老刑警劉巖度迂,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件藤乙,死亡現(xiàn)場離奇詭異,居然都是意外死亡惭墓,警方通過查閱死者的電腦和手機坛梁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腊凶,“玉大人划咐,你說我怎么就攤上這事拴念。” “怎么了褐缠?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵政鼠,是天一觀的道長。 經(jīng)常有香客問我队魏,道長缔俄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任器躏,我火速辦了婚禮俐载,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘登失。我一直安慰自己遏佣,他們只是感情好,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布揽浙。 她就那樣靜靜地躺著状婶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪馅巷。 梳的紋絲不亂的頭發(fā)上膛虫,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機與錄音钓猬,去河邊找鬼稍刀。 笑死,一個胖子當著我的面吹牛敞曹,可吹牛的內(nèi)容都是我干的账月。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼澳迫,長吁一口氣:“原來是場噩夢啊……” “哼局齿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起橄登,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤抓歼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拢锹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谣妻,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年面褐,在試婚紗的時候發(fā)現(xiàn)自己被綠了拌禾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡展哭,死狀恐怖湃窍,靈堂內(nèi)的尸體忽然破棺而出闻蛀,到底是詐尸還是另有隱情,我是刑警寧澤您市,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布觉痛,位于F島的核電站,受9級特大地震影響茵休,放射性物質(zhì)發(fā)生泄漏薪棒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一榕莺、第九天 我趴在偏房一處隱蔽的房頂上張望俐芯。 院中可真熱鬧,春花似錦钉鸯、人聲如沸吧史。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贸营。三九已至,卻和暖如春岩睁,著一層夾襖步出監(jiān)牢的瞬間钞脂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工捕儒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留冰啃,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓肋层,卻偏偏與公主長得像亿笤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子栋猖,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353