本文由 玉剛說寫做平臺 提供寫做贊助java
原做者:
四月葡萄
android版權聲明:本文版權歸微信公衆號
玉剛說
全部,未經許可,不得以任何形式轉載安全
先吐槽一下,劉海屏真醜。然而做爲苦逼的開發者,仍是要去適配劉海屏的。好了,吐槽完畢,進入正題。 微信
這裏主要是介紹一下Android P中劉海屏的適配以及Android P以前的適配。爲何要分開呢?由於Android P以前官方還沒提供API來進行適配,都是由各家廠商來提供適配方案的。Google將劉海屏命名爲屏幕缺口了,這一小節內容摘自Android官方介紹: 屏幕缺口支持。markdown
Android P 支持最新的全面屏以及爲攝像頭和揚聲器預留空間的凹口屏幕。 經過全新的 DisplayCutout
類,能夠肯定非功能區域的位置和形狀,這些區域不該顯示內容。 要肯定這些凹口屏幕區域是否存在及其位置,請使用 getDisplayCutout()
函數。ide
全新的窗口布局屬性 layoutInDisplayCutoutMode
讓您的應用能夠爲設備凹口屏幕周圍的內容進行佈局。 您能夠將此屬性設爲下列值之一:函數
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
您能夠按以下方法在任何運行 Android P 的設備或模擬器上模擬屏幕缺口:oop
目前Android支持了三類凹口屏幕類型:邊角顯示屏凹口(斜劉海)、雙顯示屏凹口(劉海+鬍子)、長型顯示屏凹口(劉海),以下圖所示: 佈局
目前的手機主要仍是長型顯示屏凹口,即劉海屏。其餘斜劉海和鬍子手機應該尚未實物吧?反正是亮瞎了狗眼了。post
注意,如下接口都是要Build.VERSION.SDK_INT >= 28
才能調用到。
主要用於獲取凹口位置和安全區域的位置等。主要接口以下所示:
方法 | 接口說明 |
---|---|
getBoundingRects() | 返回Rects的列表,每一個Rects都是顯示屏上非功能區域的邊界矩形。 |
getSafeInsetLeft () | 返回安全區域距離屏幕左邊的距離,單位是px。 |
getSafeInsetRight () | 返回安全區域距離屏幕右邊的距離,單位是px。 |
getSafeInsetTop () | 返回安全區域距離屏幕頂部的距離,單位是px。 |
getSafeInsetBottom() | 返回安全區域距離屏幕底部的距離,單位是px。 |
來看下例子。 這裏將開發者選項中的模擬具備凹口的顯示屏選項改成雙顯示屏凹口,即這裏應當有兩個劉海,而後,直接上代碼:
public class NotchActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //開局就一張背景圖 setContentView(R.layout.notch); getNotchParams(); } @TargetApi(28) public void getNotchParams() { final View decorView = getWindow().getDecorView(); decorView.post(new Runnable() { @Override public void run() { DisplayCutout displayCutout = decorView.getRootWindowInsets().getDisplayCutout(); Log.e("TAG", "安全區域距離屏幕左邊的距離 SafeInsetLeft:" + displayCutout.getSafeInsetLeft()); Log.e("TAG", "安全區域距離屏幕右部的距離 SafeInsetRight:" + displayCutout.getSafeInsetRight()); Log.e("TAG", "安全區域距離屏幕頂部的距離 SafeInsetTop:" + displayCutout.getSafeInsetTop()); Log.e("TAG", "安全區域距離屏幕底部的距離 SafeInsetBottom:" + displayCutout.getSafeInsetBottom()); List<Rect> rects = displayCutout.getBoundingRects(); if (rects == null || rects.size() == 0) { Log.e("TAG", "不是劉海屏"); } else { Log.e("TAG", "劉海屏數量:" + rects.size()); for (Rect rect : rects) { Log.e("TAG", "劉海屏區域:" + rect); } } } }); } } 複製代碼
輸出結果爲:
06-04 21:57:10.120 5698-5698/? E/TAG: 安全區域距離屏幕左邊的距離 SafeInsetLeft:0 06-04 21:57:10.120 5698-5698/? E/TAG: 安全區域距離屏幕右部的距離 SafeInsetRight:0 06-04 21:57:10.120 5698-5698/? E/TAG: 安全區域距離屏幕頂部的距離 SafeInsetTop:112 06-04 21:57:10.120 5698-5698/? E/TAG: 安全區域距離屏幕底部的距離 SafeInsetBottom:112 06-04 21:57:10.120 5698-5698/? E/TAG: 劉海屏數量:2 06-04 21:57:10.120 5698-5698/? E/TAG: 劉海屏區域:Rect(468, 0 - 972, 112) 06-04 21:57:10.120 5698-5698/? E/TAG: 劉海屏區域:Rect(468, 2448 - 972, 2560) 複製代碼
能夠看到,即距離頂部和底部各112px的區域就是安全區域了。
使用例子:
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
getWindow().setAttributes(lp);
複製代碼
Android P中新增了一個佈局參數屬性layoutInDisplayCutoutMode
,包含了三種不一樣的模式,以下所示:
模式 | 模式說明 |
---|---|
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT | 只有當DisplayCutout徹底包含在系統欄中時,才容許窗口延伸到DisplayCutout區域。 不然,窗口布局不與DisplayCutout區域重疊。 |
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER | 該窗口決不容許與DisplayCutout區域重疊。 |
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES | 該窗口始終容許延伸到屏幕短邊上的DisplayCutout區域。 |
下面咱們來寫個Demo看下這三種模式的顯示效果: Demo很簡單,就是顯示一張背景圖,相關背景佈局就不貼了,來看下主要的代碼:
public class NotchActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //去掉標題 requestWindowFeature(Window.FEATURE_NO_TITLE); //開局就一張背景圖 setContentView(R.layout.notch); //全屏顯示 getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); WindowManager.LayoutParams lp = getWindow().getAttributes(); //下面圖1 lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; //下面圖2 // lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; //下面圖3 // lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; getWindow().setAttributes(lp); } } 複製代碼
這裏設置爲全屏的顯示效果,三種模式的結果以下圖所示:
圖一能夠看到上面有黑邊。 圖二明顯看到有劉海。 圖三一樣是黑邊。能夠看到:
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
模式會讓屏幕到延申劉海區域中。LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
模式不會讓屏幕到延申劉海區域中,會留出一片黑色區域。LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
模式在全屏顯示下跟LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
同樣。
咱們再來看看LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
模式在沉浸式狀態欄下的效果,代碼以下:
public class NotchActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //去掉標題 requestWindowFeature(Window.FEATURE_NO_TITLE); //開局就一張背景圖 setContentView(R.layout.notch); //全屏顯示 // getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); //沉浸式狀態欄 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); WindowManager.LayoutParams lp = getWindow().getAttributes(); lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; getWindow().setAttributes(lp); } } 複製代碼
以下圖所示:
能夠看到:
當劉海區域徹底在系統的狀態欄時,
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
的顯示效果與LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
一致。
因此,當咱們進行劉海屏的適配時,請根據實際狀況去使用不一樣的layoutInDisplayCutoutMode
。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
將劉海區域變成一條黑色邊。上面是Android P纔有的解決方案,在P以前呢,上面的代碼統統都沒用。然而咱們偉大的國產廠商在Android P以前(基本都是Android O)就用上了高檔大氣上檔次的劉海屏,因此,這也造就了各大廠商在Android P以前的解決方案百花齊放。下面,咱們來看下主流廠商:華爲、vivo、OPPO、小米等所提供的方案。
注:相關的代碼都已封裝好,能夠直接拷貝使用。
使用新增的meta-data
屬性android.notch_support
。 在應用的AndroidManifest.xml
中增長meta-data
屬性,此屬性不只能夠針對Application
生效,也能夠對Activity
配置生效。 以下所示:
<meta-data android:name="android.notch_support" android:value="true"/> 複製代碼
Application
生效,意味着該應用的全部頁面,系統都不會作豎屏場景的特殊下移或者是橫屏場景的右移特殊處理。Activity
生效,意味着能夠針對單個頁面進行劉海屏適配,設置了該屬性的Activity
系統將不會作特殊處理。實際上還有一種代碼實現的方式,不過代碼比較多,這裏就不貼了,有興趣的話能夠在文末的連接中點進去看看。
經過如下代碼便可知道華爲手機上是否有劉海屏了,true
爲有劉海,false
則沒有。
public static boolean hasNotchAtHuawei(Context context) { boolean ret = false; try { ClassLoader classLoader = context.getClassLoader(); Class HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil"); Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen"); ret = (boolean) get.invoke(HwNotchSizeUtil); } catch (ClassNotFoundException e) { Log.e("Notch", "hasNotchAtHuawei ClassNotFoundException"); } catch (NoSuchMethodException e) { Log.e("Notch", "hasNotchAtHuawei NoSuchMethodException"); } catch (Exception e) { Log.e("Notch", "hasNotchAtHuawei Exception"); } finally { return ret; } } 複製代碼
華爲提供了接口獲取劉海的尺寸,以下:
//獲取劉海尺寸:width、height //int[0]值爲劉海寬度 int[1]值爲劉海高度 public static int[] getNotchSizeAtHuawei(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("Notch", "getNotchSizeAtHuawei ClassNotFoundException"); } catch (NoSuchMethodException e) { Log.e("Notch", "getNotchSizeAtHuawei NoSuchMethodException"); } catch (Exception e) { Log.e("Notch", "getNotchSizeAtHuawei Exception"); } finally { return ret; } } 複製代碼
vivo在設置--顯示與亮度--第三方應用顯示比例中能夠切換是否全屏顯示仍是安全區域顯示。
public static final int VIVO_NOTCH = 0x00000020;//是否有劉海 public static final int VIVO_FILLET = 0x00000008;//是否有圓角 public static boolean hasNotchAtVoio(Context context) { boolean ret = false; try { ClassLoader classLoader = context.getClassLoader(); Class FtFeature = classLoader.loadClass("android.util.FtFeature"); Method method = FtFeature.getMethod("isFeatureSupport", int.class); ret = (boolean) method.invoke(FtFeature, VIVO_NOTCH); } catch (ClassNotFoundException e) { Log.e("Notch", "hasNotchAtVoio ClassNotFoundException"); } catch (NoSuchMethodException e) { Log.e("Notch", "hasNotchAtVoio NoSuchMethodException"); } catch (Exception e) { Log.e("Notch", "hasNotchAtVoio Exception"); } finally { return ret; } } 複製代碼
vivo不提供接口獲取劉海尺寸,目前vivo的劉海寬爲100dp,高爲27dp。
OPPO目前在設置 -- 顯示 -- 應用全屏顯示 -- 凹形區域顯示控制,裏面有關閉凹形區域開關。
public static boolean hasNotchInScreenAtOPPO(Context context) { return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism"); } 複製代碼
OPPO不提供接口獲取劉海尺寸,目前其有劉海屏的機型尺寸規格都是統一的。不排除之後機型會有變化。 其顯示屏寬度爲1080px,高度爲2280px。劉海區域則都是寬度爲324px, 高度爲80px。
系統增長了 property ro.miui.notch
,值爲1時則是 Notch 屏手機。
手頭上沒有小米8的手機,暫時無法驗證,這裏就不貼代碼了,省得誤導你們。後面測試過再放出來。
小米的狀態欄高度會略高於劉海屏的高度,所以能夠經過獲取狀態欄的高度來間接避開劉海屏,獲取狀態欄的高度代碼以下:
public static int getStatusBarHeight(Context context) { int statusBarHeight = 0; int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { statusBarHeight = context.getResources().getDimensionPixelSize(resourceId); } return statusBarHeight; } 複製代碼
其餘手機也能夠經過這個方法來間接避開劉海屏,可是有可能有些手機的劉海屏高度會高於狀態欄的高度,因此這個方法獲取到的結果並不必定安全。
若是要適配其餘廠商的劉海屏,能夠去找下他們的開發者文檔,通常都會有提供的,這裏就不詳述了。