波尔多葡萄酒:Android屏幕適配很難嘛?其實也就那么回事 [復制鏈接]

2019-9-18 15:56
learnanzhuo 閱讀:513 評論:0 贊:2
Tag:  適配

恋恋波尔多 www.luaogj.com.cn 前言

作為一個Android開發人員,你還在為了適配各種尺寸的屏幕而苦惱嗎?你還在為了出現一個新的機型而修改著數不盡的dimens和layout嗎?你還在為了UI給的奇葩尺寸的設計圖而絞盡奶汁計算距離嗎?如果你為了這些事情而苦惱,那么看完這篇文章,希望可以幫你減少開發時間,減緩生命的流逝速度。

不知道大家有沒有看過前一段時間今日頭條技術團隊發表的一篇關于Android屏幕適配的文章:一種極低成本的Android屏幕適配方式。沒有看過的朋友可以先看看了解一下再回來,可以更好的理解。我是無意中點開的這篇文章,但是看過之后眼前一亮-------Android屏幕適配要是真的這么簡單,那些辛辛苦苦沒日沒夜做適配的前輩們是不是死得太慘了。

測試與思考

不得不說今日頭條的大神們的想法真的非常獨到,成本極其低廉,還特別好用。他們給出的最終方案是這樣的:

 private static float sRoncompatScaledDensity; 
private void setCustomDensity(@NonNull Activity activity, final @NonNull Application application) {
//application
final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
if (sRoncompatDennsity == 0) {
sRoncompatDennsity = appDisplayMetrics.density;
sRoncompatScaledDensity = appDisplayMetrics.scaledDensity;
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig != null && newConfig.fontScale > 0) {
sRoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
//計算寬為360dp 同理可以設置高為640dp的根據實際情況
final float targetDensity = appDisplayMetrics.widthPixels / 360;
final float targetScaledDensity = targetDensity * (sRoncompatScaledDensity / sRoncompatDennsity);
final int targetDensityDpi = (int) (targetDensity * 160);
appDisplayMetrics.density = targetDensity;
appDisplayMetrics.densityDpi = targetDensityDpi;
appDisplayMetrics.scaledDensity = targetScaledDensity;
//activity
final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.densityDpi = targetDensityDpi;
activityDisplayMetrics.scaledDensity = targetScaledDensity;
}

看到這篇文章之后我趕緊就寫了一個demo測試了一下,發現了一點小問題。我們UI給出的設計圖尺寸為1334*720,如果我按照寬度作為適配標準的話,按照設計圖720px的寬度,屏幕的寬度應為360dp,也就是這樣:

final float targetDensity = appDisplayMetrics.widthPixels / 360; 

這樣做的話寬度適配的比例是沒有任何問的,但是我在想,如果需要以高度來做適配(也就是內容剛好縱向填充全屏)的話,是不是改成這樣就可以了:

final float targetDensity = appDisplayMetrics.heightPixels / 667; 

但是運行之后發現,高度上的差異很大,運行在不同分辨率和尺寸的手機上,頁面中的每一部分內容在縱向上的比例不盡相同,沒有達到很好的適配的效果。思考了許久過后我發現一個問題:我手邊的測試機的寬度是兩個720和兩個1080,而高度有1280,1440,1780和一個全面屏的2160。Android的開原性導致了Android設備的尺寸的碎片化太嚴重,而通過查看手機的尺寸參數會發現,如果用這四個手機來測試的話,寬度可以直接整除,而高度不可以(并且我手邊的測試機的寬度也可以整除,如果有寬度沒法整除的手機呢?)。但是用今日頭條給出的方法,做除法后結果會取整,那會不會是由于用縱向計算出來的density取整影響了精度,從而導致了效果不盡人意呢?

問題修復

發現上述問題之后我就著手去修改,將計算結果取余后在賦值給targetDensity,經過一下午的反復測試與實驗,我重新修改了targetDensity的計算方法:

float targetDensity = 0; 
try {
Double division = Operation.division(appDisplayMetrics.heightPixels, 667);
//由于手機的長寬不盡相同,肯定會有除不盡的情況,有失精度,所以在這里把所得結果做了一個保留兩位小數的操作
DecimalFormat df = new DecimalFormat("0.00");
String s = df.format(division);
targetDensity = Float.parseFloat(s);
} catch (NumberFormatException e) {
e.printStackTrace();
}

經測試后發現,這樣取兩位小數計算過后,高度上的適配結果讓人非常滿意??墑腔褂幸桓鑫侍?,我們一般來說做適配都是以手機的寬度為基準,但是一個app里面避免不了偶爾一兩個頁面是按照高度為基準(就是內容縱向填充全屏的頁面)做適配的。但是上述方法只能保證一個方向,那我就讓它可以自由的切換適配的基準方向不就好了。

最終方案

繼續修改之后我得到了最終的方案,修改過后這個類中的所有內容如下:

private static float appDensity; 
private static float appScaledDensity;
private static DisplayMetrics appDisplayMetrics;
//此方法在Application的onCreate方法中調用 Density.setDensity(this);
public static void setDensity(@NonNull Application application) {
//獲取application的DisplayMetrics
appDisplayMetrics = application.getResources().getDisplayMetrics();
if (appDensity == 0) {
//初始化的時候賦值(只在Application里面初始化的時候會調用一次)
appDensity = appDisplayMetrics.density;
appScaledDensity = appDisplayMetrics.scaledDensity;
//添加字體變化的監聽
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
//字體改變后,將appScaledDensity重新賦值
if (newConfig != null && newConfig.fontScale > 0) {
appScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
//調用修改density值的方法(默認以寬度作為基準)
setAppOrientation(null, AppUtils.WIDTH);
}
//此方法用于在某一個Activity里面更改適配的方向 Density.setOrientation(mActivity, "width/height");
public static void setOrientation(Activity activity, String orientation) {
setAppOrientation(activity, orientation);
}
/**
* targetDensity
* targetScaledDensity
* targetDensityDpi
* 這三個參數是統一修改過后的值
*
* orientation:方向值,傳入width或height
*/
private static void setAppOrientation(@Nullable Activity activity, String orientation) {
float targetDensity = 0;
try {
Double division;
//根據帶入參數選擇不同的適配方向
if (orientation.equals("height")) {
//appDisplayMetrics.heightPixels/667
division = Operation.division(appDisplayMetrics.heightPixels, 667);
} else {
division = Operation.division(appDisplayMetrics.widthPixels, 360);
}
//由于手機的長寬不盡相同,肯定會有除不盡的情況,有失精度,所以在這里把所得結果做了一個保留兩位小數的操作
DecimalFormat df = new DecimalFormat("0.00");
String s = df.format(division);
targetDensity = Float.parseFloat(s);
} catch (NumberFormatException e) {
e.printStackTrace();
}
float targetScaledDensity = targetDensity * (appScaledDensity / appDensity);
int targetDensityDpi = (int) (160 * targetDensity);
/**
*
* 最后在這里將修改過后的值賦給系統參數
*
* (因為最開始初始化的時候,activity為null,所以只設置application的值就可以了...
* 所以在這里判斷了一下,如果傳有activity的話,再設置Activity的值)
*/
if (activity != null) {
DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.scaledDensity = targetScaledDensity;
activityDisplayMetrics.densityDpi = targetDensityDpi;
} else {
appDisplayMetrics.density = targetDensity;
appDisplayMetrics.scaledDensity = targetScaledDensity;
appDisplayMetrics.densityDpi = targetDensityDpi;
}
}

這是修改之后的所有內容,不懂的地方可以看一下里面的注釋,在里面我是默認的以寬度來作為基準(這是在Activity中設置的方法,存在于此Activity下的fragment,dialog和PopupWindow都會受到此效果的影響,也就是說,在Activity中設置一次之后,Activity下的其他子View都無需再設置一次)。

使用方法

自己創建一個類,將最終方案里面的代碼復制粘貼就可以使用了

使用方法:在Application的onCreate()方法中


Android屏幕適配很難嘛?其實也就那么回事


image.png

如果只是適配一個方向的話,只設置這一句就可以了(我在utils里面設置了默認按照寬度適配,可以根據自己的需求修改默認的適配方向,見下圖)


Android屏幕適配很難嘛?其實也就那么回事


若app中有某一個頁面需要縱向適配的話:

/** 
*
* 由于是個人封裝,此方法需要寫在onCreate()中的setContentView()方法前面,切換方向的效果才會生效
*/
@Override
public void setOrientation() {
Density.setOrientation(this, AppUtils.HEIGHT);
}
/**
*
* 如果在一個Activity里面切換了適配方向的話,需要在destroy里面將方向設置為默認的方向,
* 因為切換方向修改的是Activity的值,但是application的也會覆蓋掉(原因還沒有搞清楚...),
* 權衡利弊之后就在onDestroy這個生命周期里面重新初始化了一下方向(因為用高度作為適配基準的頁面
* 少之又少,這樣可以最大程度的減少對程序功能性的影響)
*/
@Override
protected void onDestroy() {
super.onDestroy();
Density.setOrientation(this, AppUtils.WIDTH);
}

由于在某一個Activity里面切換方向之后,我修改掉的是Activity中的值(activityDensity),但是返回再點擊其他頁面之后發現其他頁面的適配方向也被修改掉了,于是乎權衡利弊之后我就用了這個相對來說影響最小的辦法:在需要修改適配方向的Activity中的onDetroy生命周期里面,再手動將方向改成默認。。。(搗鼓了很久實在是想不到更好的辦法了,如果各位看官有其他的好辦法可以給我留言)。


Android屏幕適配很難嘛?其實也就那么回事


最后貼出縱向適配的效果圖,頁面中藍色背景的TextView高度是固定的150dp(只是我自己寫的一個很簡單的頁面,不要嫌丑。。。):



敲黑板!!!

用此方法寫適配,只需要一個dimens文件,一個layout文件就足矣,在xml布局中直接只用dp就可以了(Android P的劉海屏需要單獨適配layout,全面屏手機可以隱藏的虛擬按鍵似乎也需要單獨適配。。。)


Android屏幕適配很難嘛?其實也就那么回事



我來說兩句
您需要登錄后才可以評論 登錄 | 立即注冊
facelist
所有評論(0)
領先的中文移動開發者社區
18620764416
7*24全天服務
意見反?。[email protected]

掃一掃關注我們

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 恋恋波尔多 )