android設備各類各樣,手機、pad、電視、車載等不一而足。即便是相同分辨率的手機也可能參數不一致,好比1080P的手機 dpi 通常認爲是480,可是 Google 的Pixel2(1920*1080)的 dpi 是420。此外,android設備的寬高比更是多種多樣。這就致使App適配的工做異常困難。尤爲是你的app要適配各類平臺,好比手機、pad、車載、電視。在這種情形下,你面臨的問題讓你無所適從,由於你根本猜不到設備的參數和尺寸,更別提如何適配。android
android度量計算公式git
具體的含義自行搜索,density 的差別致使適配困難;scaledDensity 是字體的縮放因子,scaledDensity 正常狀況下和 density 相等,可是調節系統字體大小後會改變這個值。
查看源碼,能夠得知:DisplayMetrics 實例經過 Resources#getDisplayMetrics能夠得到,而Resouces經過 Activity 或者 Application 的 Context 得到。
dp 和 px 的轉換是經過 DisplayMetrics 中相關的值來計算的,view、bitmap 等元素在計算中的dp轉換也是如此。
佈局文件中 dp 的轉換,最終都是調用 TypedValue#applyDimension(int unit, float value, DisplayMetrics metrics) 來進行轉換。相似的,BitmapFactory#decodeResourceStream 方法也會應用 DisplayMetrics 中的參數計算。github
/**
* Converts an unpacked complex data value holding a dimension to its final floating
* point value. The two parameters <var>unit</var> and <var>value</var>
* are as in {@link #TYPE_DIMENSION}.
*
* @param unit The unit to convert from.
* @param value The value to apply the unit to.
* @param metrics Current display metrics to use in the conversion --
* supplies display density and scaling information.
*
* @return The complex floating point value multiplied by the appropriate
* metrics depending on its unit.
*/
public static float applyDimension(int unit, float value,
DisplayMetrics metrics)
{
switch (unit) {
case COMPLEX_UNIT_PX:
return value;
case COMPLEX_UNIT_DIP:
return value * metrics.density;
case COMPLEX_UNIT_SP:
return value * metrics.scaledDensity;
case COMPLEX_UNIT_PT:
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
return value * metrics.xdpi;
case COMPLEX_UNIT_MM:
return value * metrics.xdpi * (1.0f/25.4f);
}
return 0;
}
複製代碼
修改application和activity的density,系統修改字體時打開App也能對應修改。scaledDensity計算根據系統原來的比值來得到如今修改後的值。api
final float targetScaledDensity = targetDensity * (appDisplayMetrics.scaledDensity / appDisplayMetrics.density);
複製代碼
在 Activity#onCreate 方法中調用下。代碼比較簡單,也沒有涉及到系統非公開api的調用,所以理論上不會影響app穩定性。bash
/**
* 頭條處理多設備的方案 setCustomDensity(this, getApplication());
*
* @param activity
* @param application
*/
private void setCustomDensity(Activity activity, final 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;
}
複製代碼
具體適配效果能夠見參考資料連接,今日頭條團隊在各類手機上的適配效果和後期bug反饋。app
這個方案的解決思路很簡潔,參考資料也詳細的列舉了它的優勢,很是吸引人。可是,最終咱們公司的項目沒有采用這個,而是採用下面的方案。理由很簡單:一次修改,全局改變。後期維護無所適從。假如一處UI出問題了,你打算怎麼改?你無法改,你怎麼改。ide
參考中《騷年你的屏幕適配方式該升級了!-今日頭條適配方案》這篇文章進一步升級了這個思路,它最大的貢獻是對單個 Activity 或 Fragment 能夠取消適配。這個思路能夠解決後期維護問題,我以爲這個方案這個時候就值得推薦和使用了。同時,它還能自定義以寬或者高爲維度進行適配。佈局
遍歷 ViewGroup 獲取全部子 View 的尺寸參數,從新計算 View 的WidthHeightFont、Padding、LayoutMargin。字體
/**
* Only adapter width/height/padding/margin
* Created by zhangyuwan0 on 2018/3/21.
*/
public class SimpleConversion implements IConversion {
@Override
public void transform(View view, AbsLoadViewHelper loadViewHelper) {
if (view.getLayoutParams() != null) {
loadViewHelper.loadWidthHeightFont(view);
loadViewHelper.loadPadding(view);
loadViewHelper.loadLayoutMargin(view);
}
}
}
複製代碼
Activity、Fragment、自定義 View 等加載view後,手動調用LoadViewHelper#loadView 方法重計算一遍全部view。本質的轉化方法是計算縮放因子。ui
private float calculateValue(float value) {
if ("px".equals(unit)) {
return value * ((float) actualWidth / (float) designWidth);
} else if ("dp".equals(unit)) {
int dip = dp2pxUtils.px2dip(actualDensity, value);
value = ((float) designDpi / 160) * dip;
return value * ((float) actualWidth / (float) designWidth);
}
return 0;
}
複製代碼
/**
* Created by guokun on 2018/7/21.
* Description: 標準寬高640x360(16:9) density = 1.0 dpi = 160
* 1. 高度低於設計高度,以高度做標準縮放;
* 2. 高度高於設計高度,可是高度:寬度 < 9:16,以高度做標準縮放;
* 3. 其他以寬度做標準縮放;
* @param
* @return
*/
public float calculateValue(float value) {
if ("px".equals(unit)) {
return value * ((float) actualWidth / (float) designWidth);
} else if ("dp".equals(unit)) {
int dip = dp2pxUtils.px2dip(actualDensity, value);
value = ((float) designDpi / 160) * dip;
if (actualHeight < designHeight || actualWidth * designHeight / designWidth > actualHeight) {
return value * ((float) actualHeight / (float) designHeight);
}
return value * ((float) actualWidth / (float) designWidth);
}
return 0;
}
複製代碼
@Override
public void transform(View view, AbsLoadViewHelper loadViewHelper) {
/**Created by guokun on 2018/7/28.
* Description: MyLinearLayout_h381特殊處理
* 1. MyLinearLayout鍵盤的高度大於標準高度360;
* 2. 這裏UI標準圖設計bug,未加上20dp 鍵盤top;
* */
if (view.getTag() != null && (Integer)view.getTag() == MyLinearLayout_h381.getCustomHeight(view.getContext())) {
int defaultDesign = loadViewHelper.getDesignHeight();
loadViewHelper.setDesignHeight((Integer) view.getTag());
if (view.getLayoutParams() != null) {
loadViewHelper.loadWidthHeightFont(view);
loadViewHelper.loadPadding(view);
loadViewHelper.loadLayoutMargin(view);
}
loadViewHelper.setDesignHeight(defaultDesign);
}else {
if (view.getLayoutParams() != null) {
loadViewHelper.loadWidthHeightFont(view);
loadViewHelper.loadPadding(view);
loadViewHelper.loadLayoutMargin(view);
}
}
}
複製代碼
放棄這個項目吧,不值得。光是缺點,你都改不過來。
android 適配一直是個懸而未決的大難題。Google 提供的思路對於國內複雜的設備環境和小團隊而言,代價很高。綜合項目實際場景再權衡各類方案纔是解決之道,由於這些方案自己並非很大的工程。
推薦Android兩種屏幕適配方案
Android 目前穩定高效的UI適配方案
騷年你的屏幕適配方式該升級了!-今日頭條適配方案