爲何先要解釋一下全面屏,由於這個詞在如今來說就是一個僞命題。全面屏字面意思就是手機的正面所有都是屏幕,100%的屏佔比。可是如今推出所謂「全面屏」手機的廠商沒有一個能達到全面的。html
那麼下面來講一下Android開發領域對全面屏的理解和定義吧。java
通常手機的屏幕縱橫比爲16:9,如1080x1920、1440x2560等,其比值爲1.77,在全面屏手機出現以前,Android中默認的最大屏幕縱橫比(maximum aspect ratio)爲1.86,即可以兼容16:9的屏幕。android
一些手機廠商爲了追求更大的屏幕空間以及更極致的用戶體驗,因而提升了屏幕縱橫比,17:九、19:十、18:九、18.5:9的手機開始進入市場,這些手機的屏幕縱橫比大大超過了1.86,這些手機被稱爲全面屏手機。bash
咱們將targetSdkVersion的值改成小於等於23,運行程序,咱們會發現屏幕底部出現一個黑條。app
在Galaxy S8發佈以後,Android官方提供了適配方案,即提升App所支持的最大屏幕縱橫比,實現很簡單,在AndroidManifest.xml中可作以下配置:ide
<meta-data android:name="android.max_aspect" android:value="ratio_float"/>
複製代碼
其中ratio_float爲浮點數,官方建議爲2.1或更大,由於18.5:9=2.055555555……,若是往後出現縱橫比更大的手機,此值將會更大。佈局
<meta-data android:name="android.max_aspect" android:value="2.1" />
複製代碼
max_aspect值也能夠在Java代碼中動態地設置,經過下面的方法便可實現:優化
public void setMaxAspect() {
ApplicationInfo applicationInfo = null;
try {
applicationInfo = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if(applicationInfo == null){
throw new IllegalArgumentException(" get application info = null, has no meta data! ");
}
applicationInfo.metaData.putString("android.max_aspect", "2.1");
}
複製代碼
若是targetSdkVersion的值的值大於23,那麼應該不用設置max_aspect便可。ui
查看適配以後的截圖:this
android-developers.googleblog.com/2017/03/upd…
咱們看一下啓動頁,在16:9屏幕中適配的圖片,到了18:9的屏幕中就會被拉伸了。
16:9屏幕中顯示 | 18:9屏幕中顯示 |
---|---|
解決這個問題無非就是兩種方法,換圖片或者是換佈局
不能依賴單一廠商的解決方案,只能從Android系統屬性出發。考慮到目前大部分全面屏手機只是在高度上拉長,且大多爲6.0英寸左右,像素密度對比xxhdpi並無多大區別,那咱們能夠在項目中增長一組資源drawable-xxhdpi-2160x1080 、drawable-long 這樣解決圖片的拉伸問題,固然這樣的方法確定是不太好的,會增長app的容量。這裏就不演示了。
固然最好的方法仍是用相對佈局採用XML的方式,或者.9圖的解決方案。
我總結的就是少許多切,儘可能減小尺寸對佈局的影響。好比這裏,使用正方形的切圖,讓他居中顯示,不管屏幕縱橫好比何,都不會拉伸這個圖片,拉伸的只是背景而已。
<ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="fitCenter" android:src="@drawable/bz002"/>
複製代碼
適配前 | 適配後 |
---|---|
首先解釋一下window,decorview,rootview這幾個概念
Window官方文檔:Window
public abstract class Window. Abstract base class for a top-level window look and behavior policy. An instance of this class should be used as the top-level view added to the window manager. It provides standard UI policies such as a background, title area, default key processing, etc.
The only existing implementation of this abstract class is android.view.PhoneWindow, which you should instantiate when needing a Window.
翻譯一下:每個 Activity 都持有一個 Window 對象,可是 Window 是一個抽象類,這裏 Android 爲 Window 提供了惟一的實現類 PhoneWindow。也就是說 Activity 中的 window 實例就是一個 PhoneWindow 對象。
可是 PhoneWindow 終究是 Window,它並不具有多少 View 相關的能力。不過 PhoneWindow 中持有一個 Android 中很是重要的一個 View 對象 DecorView.
如今的關係就很明確了,每個 Activity 持有一個 PhoneWindow 的對象,而一個 PhoneWindow 對象持有一個 DecorView 的實例,因此 Activity 中 View 相關的操做其實大都是經過 DecorView 來完成。
DecorView就能夠理解爲手機的內屏,就是那塊玻璃,能夠發光的屏幕。
這裏經過代碼,打印出咱們頁面中的高度的各項數據
int decorviewHeight = decorView.getHeight();
int screenHeight = FullScreenManager.getScreenHeight();
int nativeBarHeight = FullScreenManager.getNativeBarHeight();
int contentViewHeight = rootView.getHeight();
int navigationBarHeight1 = FullScreenManager.getNavigationBarHeight();
Log.d("shijiacheng","=======================================");
Log.d("shijiacheng","DecorView height: " + decorviewHeight + " px");
Log.d("shijiacheng","Screen height: " + screenHeight + " px");
Log.d("shijiacheng","NativeBar height: " + nativeBarHeight + " px");
Log.d("shijiacheng","ContentView height: " + contentViewHeight + " px");
Log.d("shijiacheng","NavigationBar height: " + navigationBarHeight + " px");
Log.d("shijiacheng","---------------------------------------");
複製代碼
final View decorView = getWindow().getDecorView();
int decorviewHeight = decorView.getHeight();
複製代碼
/** * 得到屏幕高度 * @return */
public static int getScreenHeight() {
Resources resource = AppContext.getInstance().getResources();
DisplayMetrics displayMetrics = resource.getDisplayMetrics();
return displayMetrics.heightPixels;
}
複製代碼
/** * 獲取狀態欄的高度 * * @return */
public static int getNativeBarHeight() {
Resources resource = AppContext.getInstance().getResources();
int result = 0;
int resourceId = resource.getIdentifier("status_bar_height",
"dimen", "android");
if (resourceId > 0) {
result = resource.getDimensionPixelSize(resourceId);
}
return result;
}
複製代碼
LinearLayout contentView = findViewById(R.id.root);
int contentViewHeight = contentView.getHeight();
複製代碼
public static int getNavigationBarHeight() {
Resources resources = AppContext.getInstance().getResources();
int resourceId = resources.getIdentifier("navigation_bar_height","dimen", "android");
int height = resources.getDimensionPixelSize(resourceId);
return height;
}
複製代碼
爲了更加直觀的展現各個數據,這裏咱們使用佈局的方式將各個數據展現出來,佈局代碼比較簡單,這裏就不展現了。
先展現一下正常的屏幕高度的各項數據
10-08 09:52:03.636 23818-23818/? D/shijiacheng: =========================
10-08 09:52:03.637 23818-23818/? D/shijiacheng: DecorView height: 1280 px
10-08 09:52:03.637 23818-23818/? D/shijiacheng: Screen height: 1280 px
10-08 09:52:03.637 23818-23818/? D/shijiacheng: NativeBar height: 50 px
10-08 09:52:03.637 23818-23818/? D/shijiacheng: ContentView height: 1230 px
10-08 09:52:03.637 23818-23818/? D/shijiacheng: NavigationBar height: 96 px
10-08 09:52:03.637 23818-23818/? D/shijiacheng: -------------------------
複製代碼
DecorView = Screen height = NativeBar height + ContentView height
看一下小米mix全面屏的狀況
2018-10-08 09:54:15.640 /? D/shijiacheng: =========================
2018-10-08 09:54:15.640 /? D/shijiacheng: DecorView height: 2160 px
2018-10-08 09:54:15.641 /? D/shijiacheng: RootView height: 2094 px
2018-10-08 09:54:15.641 /? D/shijiacheng: Screen height: 2030 px
2018-10-08 09:54:15.641 /? D/shijiacheng: NativeBar height: 66 px
2018-10-08 09:54:15.641 /? D/shijiacheng: ContentView height: 2094 px
2018-10-08 09:54:15.641 /? D/shijiacheng: NavigationBar height: 130 px
2018-10-08 09:54:15.641 /? D/shijiacheng: -------------------------
複製代碼
問題出現了,能夠發現contentView的高度比screen屏幕的高度還要大,不由要懷疑,咱們的獲取屏幕高度的方法在全面屏下計算錯誤了。
咱們一直都是使用以下方法進行屏幕高度測量的:
public static int getScreenHeight() {
Resources resource = AppContext.getInstance().getResources();
DisplayMetrics displayMetrics = resource.getDisplayMetrics();
return displayMetrics.heightPixels;
}
複製代碼
可是這個方法倒是一個十分古老的方法,沒有與時俱進,雖說在普通屏幕上這種方法沒有問題,可是在全面屏手機上來講,這種方法就不靈了。
下面咱們就來研究一下獲取屏幕尺寸的方法的演進。
獲取屏幕的寬高是咱們開發中常常遇到的問題,並且相信你們都已經很是熟悉,最經常使用的爲如下兩種:
public static int getScreenHeight1(Activity activity) {
return activity.getWindowManager().getDefaultDisplay().getHeight();
}
public static int getScreenHeight2(Activity activity) {
DisplayMetrics displayMetrics = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
return displayMetrics.heightPixels;
}
複製代碼
其實以上兩種方式是同樣的,只不過第二種是把信息封裝到 DesplayMetrics中,再從DesplayMetrics獲得數據。
在 Android 3.2(Api 13) 以後又提供了以下的一個方法,將數據封裝到Point中,而後返回寬度高度信息。
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
public static int getScreenHeight3(Activity activity) {
Point point = new Point();
activity.getWindowManager().getDefaultDisplay().getSize(point);
return point.y;
}
複製代碼
在 Android 4.2(Api17) 以後提供了以下方法,與第三種相似也是將數據封裝到Point中,而後返回款高度信息。
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static int getScreenHeight4(Activity activity) {
Point realSize = new Point();
activity.getWindowManager().getDefaultDisplay().getRealSize(realSize);
return realSize.y;
}
複製代碼
其實getRealSize這個方法在Android Api15的時候就已經加入了,不過是被隱藏了,經過查閱源碼咱們能夠看到。
Android Api15 Display.java源碼中getRealSize()方法被標記爲@hide
所以,咱們能夠重寫獲取高度的方法,適配全部機型,全部系統。
public static int[] getScreenSize(Context context) {
int[] size = new int[2];
WindowManager w = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display d = w.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
d.getMetrics(metrics);
// since SDK_INT = 1;
int widthPixels = metrics.widthPixels;
int heightPixels = metrics.heightPixels;
// includes window decorations (statusbar bar/menu bar)
if (Build.VERSION.SDK_INT >= 14 && Build.VERSION.SDK_INT < 17)
try {
widthPixels = (Integer) Display.class.getMethod("getRawWidth").invoke(d);
heightPixels = (Integer) Display.class.getMethod("getRawHeight").invoke(d);
} catch (Exception ignored) {
}
// includes window decorations (statusbar bar/menu bar)
if (Build.VERSION.SDK_INT >= 17)
try {
Point realSize = new Point();
Display.class.getMethod("getRealSize", Point.class).invoke(d, realSize);
widthPixels = realSize.x;
heightPixels = realSize.y;
} catch (Exception ignored) {
}
size[0] = widthPixels;
size[1] = heightPixels;
return size;
}
複製代碼
使用新的獲取高度的方法,從新運行程序,運行結果已經正常顯示了。
2018-10-08 13:19:32.389 /? D/shijiacheng: ==========================
2018-10-08 13:19:32.390 /? D/shijiacheng: DecorView height: 2160 px
2018-10-08 13:19:32.390 /? D/shijiacheng: Screen height: 2160 px
2018-10-08 13:19:32.390 /? D/shijiacheng: NativeBar height: 66 px
2018-10-08 13:19:32.390 /? D/shijiacheng: ContentView height: 2094 px
2018-10-08 13:19:32.390 /? D/shijiacheng: NavigationBar height: 130 px
2018-10-08 13:19:32.390 /? D/shijiacheng: --------------------------
複製代碼
咱們在MIUI設置中將全面屏導航樣式修改成「經典導航鍵」樣式。
從新運行程序,運行結果以下:
能夠發現又出問題了,DecorView = Screen height > NativeBar height + ContentView height
這裏不難發現,Screen height將底部虛擬導航欄的高度也算進裏面了。
不少狀況下,咱們都用以下方法獲取導航欄的高度:
public static int getNavigationBarHeight() {
Resources resources = AppContext.getInstance().getResources();
int resourceId = resources.getIdentifier("navigation_bar_height","dimen", "android");
int height = resources.getDimensionPixelSize(resourceId);
return height;
}
複製代碼
這種方法獲得的導航欄的高度數值是沒問題的,可是在全面屏的手機上,即便隱藏了導航欄,也是能夠獲取到導航欄的高度的。經過上面的logcat日誌能夠看到,即便沒有導航欄,導航欄的高度的計算也是有值的。
小米mix的機型中,咱們能夠「force_fsg_nav_bar」來判斷小米手機是否開啓了全面屏手勢。
public static int getHeightOfNavigationBar(Context context) {
//若是小米手機開啓了全面屏手勢隱藏了導航欄則返回 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
if (Settings.Global.getInt(context.getContentResolver(), "force_fsg_nav_bar", 0) != 0) {
return 0;
}
}
int realHeight = getScreenSize(context)[1];
Display d = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
DisplayMetrics displayMetrics = new DisplayMetrics();
d.getMetrics(displayMetrics);
int displayHeight = displayMetrics.heightPixels;
return realHeight - displayHeight;
}
複製代碼
所以能夠經過這個方法來判斷是否顯示了底部導航欄,而且能夠計算導航欄的高度。
int navigationBarHeight = FullScreenManager.getHeightOfNavigationBar(MainActivity.this);
if (navigationBarHeight > 0){
container_navigationview.setVisibility(View.VISIBLE);
}else {
container_navigationview.setVisibility(View.GONE);
}
複製代碼
正常的顯示效果以下:
有虛擬導航欄 | 沒有虛擬導航欄 |
---|---|
沒有虛擬導航欄Log
2018-10-08 13:19:32.389 /? D/shijiacheng: ==========================
2018-10-08 13:19:32.390 /? D/shijiacheng: DecorView height: 2160 px
2018-10-08 13:19:32.390 /? D/shijiacheng: Screen height: 2160 px
2018-10-08 13:19:32.390 /? D/shijiacheng: NativeBar height: 66 px
2018-10-08 13:19:32.390 /? D/shijiacheng: ContentView height: 2094 px
2018-10-08 13:19:32.390 /? D/shijiacheng: NavigationBar height: 0 px
2018-10-08 13:19:32.390 /? D/shijiacheng: --------------------------
複製代碼
有虛擬導航欄Log
2018-10-08 13:38:03.229 /? D/shijiacheng: ==========================
2018-10-08 13:38:03.230 /? D/shijiacheng: DecorView height: 2160 px
2018-10-08 13:38:03.230 /? D/shijiacheng: Screen height: 2160 px
2018-10-08 13:38:03.230 /? D/shijiacheng: NativeBar height: 66 px
2018-10-08 13:38:03.230 /? D/shijiacheng: ContentView height: 1964 px
2018-10-08 13:38:03.230 /? D/shijiacheng: NavigationBar height: 130 px
2018-10-08 13:38:03.230 /? D/shijiacheng: --------------------------
複製代碼