學習一種極低成本的Android屏幕適配方式
學習今日頭條技術團隊-->一種極低成本的Android屏幕適配方式
JessYan->騷年你的屏幕適配方式該升級了!-今日頭條適配方案
今日頭條屏幕適配方案終極版正式發(fā)布!
文章中剛開的的計算供著幾個參數就沒弄懂,搜了2遍不錯的文章
兩分鐘理解Android中PX、DP、SP的區(qū)別
Android 中 px、dp、dip、sp詳解
再回來看計算公式:
- px = density * dp;
- density = dpi / 160;
- px = dp * (dpi / 160);
而為什么除以160,在Google官方文檔也有說明,密度無關像素(dp)等于 160 dpi 屏幕上的一個物理像素,這是 系統(tǒng)為“中”密度屏幕假設的基線密度。
了解了是怎么一回事。
下表格出處Google官方文檔
| 屏幕特性 | 限定符 | 說明 |
|---|---|---|
| 尺寸 | small | 適用于小尺寸屏幕的資源。 |
| normal | 適用于正常尺寸屏幕的資源。(這是基線尺寸。) | |
| large | 適用于大尺寸屏幕的資源。 | |
| xlarge | 適用于超大尺寸屏幕的資源。 | |
| 密度 | ldpi | 適用于低密度 (ldpi) 屏幕 (~120dpi) 的資源。 |
| mdpi | 適用于中密度 (mdpi) 屏幕 (~160dpi) 的資源。(這是基線 密度。) | |
| hdpi | 適用于高密度 (hdpi) 屏幕 (~240dpi) 的資源。 | |
| xhdpi | 適用于超高密度 (xhdpi) 屏幕 (~320dpi) 的資源。 | |
| xxhdpi | 適用于超超高密度 (xxhdpi) 屏幕 (~480dpi) 的資源。 | |
| xxxhdpi | 適用于超超超高密度 (xxxhdpi) 屏幕 (~640dpi) 的資源。此限定符僅適用于 啟動器圖標,請參閱上面的注。 | |
| nodpi | 適用于所有密度的資源。這些是密度獨立的資源。不管當前屏幕的密度如何,系統(tǒng)都不會 縮放以此限定符標記的資源。 | |
| tvdpi | 適用于密度介于 mdpi 和 hdpi 之間屏幕(約為 213dpi)的資源。它并不是 “主要”密度組,主要用于電視,而大多數應用都不 需要它 — 對于大多數應用而言,提供 mdpi 和 hdpi 資源便已足夠,系統(tǒng)將根據需要對其進行 縮放。如果發(fā)現(xiàn)必須提供 tvdpi 資源,應以 1.33*mdpi 的系數 調整其大小。例如,mdpi 屏幕的 100px x 100px 圖像應該相當于 tvdpi 的 133px x 133px。 | |
| 方向 | land | 適用于橫屏(長寬比)的資源。 |
| port | 適用于豎屏(高寬比)的資源。 | |
| 縱橫比 | long | 適用于縱橫比明顯高于或寬于(分別在豎屏 或橫屏時)基線屏幕配置的屏幕的資源。 |
| notlong | 適用于使用縱橫比類似于基線屏幕 配置的屏幕的資源。 |
關鍵
px = dp * density
從上面公式中,我們可以了解到我在布局文件設置相同的dp,但在不同的設備顯示不同,唯一可以變化的就是desity所以我們只要修改destiny,來滿足我們需要平米顯示的px值。
destiny是DisplayMetrics中的成員變量,我們可以通過Resources#getDisplayMetrics獲取我們需要的數據。
我們需要關注的幾個變量:
-
DisplayMetrics#density就是上述的density -
DisplayMetrics#densityDpi就是上述的dpi -
DisplayMetrics#scaledDensity字體的縮放因子,正常情況下和density相等,但是調節(jié)系統(tǒng)字體大小后會改變這個值
然后,可以了解到
-
布局文件中dp的轉換,最終都是調用 TypedValue#applyDimension(int unit, float value, DisplayMetrics metrics) 來進行轉換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 return 0; } -
圖片中的decode,
BitmapFactory#decodeResourceStream方法:public static Bitmap decodeResourceStream(Resources res, TypedValue value, InputStream is, Rect pad, Options opts) { //省略不想干的代碼 if (opts.inTargetDensity == 0 && res != null) { opts.inTargetDensity = res.getDisplayMetrics().densityDpi; } return decodeStream(is, pad, opts); }
通過上面2個我們了解到了,基本都是通過 DisplayMetrics來計算的。所以了解了上面的到下面的最終方案。
最終方案
今日頭條用的是360dp(360*640),以寬維度來適配的。
private static float sComponentDensity;
private static float sComponentScaledDensity;
/**
* 設置自定義的屏幕密度
* 在BaseActivity中的onCreate()方法中調用
*/
private static void setCustomDensity(@NonNull Activity activity, @NonNull final Application application){
final DisplayMetrics appDisplayMetrics = application.getResources().getDisplayMetrics();
if (sComponentDensity == 0) {
sComponentDensity = appDisplayMetrics.density;
sComponentScaledDensity = appDisplayMetrics.scaledDensity;
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig != null && newConfig.fontScale > 0) {
sComponentScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
final float targetDensity = appDisplayMetrics.widthPixels / 360F;//以360dp(360\*640)和寬維度來適配的
final float targetScaledDensity = targetDensity * (sComponentScaledDensity / sComponentDensity);
final int targetDensityDpi = (int) (160 * targetDensity);
//賦值到application中的density中
appDisplayMetrics.density = targetDensity;
appDisplayMetrics.scaledDensity = targetScaledDensity;
appDisplayMetrics.densityDpi = targetDensityDpi;
//下面賦值到activity中的density中
final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
appDisplayMetrics.scaledDensity = targetScaledDensity;
activityDisplayMetrics.densityDpi= targetDensityDpi;
}