android中的dp在渲染前會將dp轉爲px,計算公式:
-
px = density * dp;
-
density = dpi / 160;
-
px = dp * (dpi / 160)
而dpi是根據屏幕真實的分辨率和尺寸來計算的,每個設備都可能不一樣的。
1.屏幕分辨率限定符適配
根據當前市面上手機的屏幕的分辨率創建不同的文件夾,系統運行的時候,會自動去選擇讀取對應的文件夾中的xml,即每種屏幕分辨率的設備需要定義一套 dimens.xml 文件
缺點是:假設我們UI設計圖是按屏幕寬度爲360dp來設計的,那麼在上述設備上,屏幕寬度其實爲1080/(440/160)=392.7dp,也就是屏幕是比設計圖要寬的。這種情況下, 即使使用dp也是無法在不同設備上顯示爲同樣效果的。 同時還存在部分設備屏幕寬度不足360dp,這時就會導致按360dp寬度來開發實際顯示不全的情況
而且上述屏幕尺寸、分辨率和像素密度的關係,很多設備並沒有按此規則來實現, 因此dpi的值非常亂,沒有規律可循,從而導致使用dp適配效果差強人意。
2.今日頭條適配方案(字節跳動)
在上面的兩種適配方案中,都是根據不同的屏幕大小設置不同的控件的寬高值。在公式 px =dp * density 中,不同屏幕的dp,以及density都是不同,從而實現不同的手機渲染有着不同的px。在今日頭條的適配方案的思路是假定每個手機的屏幕寬度是固定的。比如設計稿寬度是360dp, 想要保證在所有設備計算得出的px值都正好是屏幕寬度的話,只能修改 density 的值。
在xml中設置寬高後,都是通過以下代碼進行轉換:
http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/util/TypedValue.java#applyDimension
當在xml中設置的單位爲px, 則直接用對應px的值;當單位爲dp,則返回的是:value * metrics.density;當單位爲sp,則返回的是value * metrics.scaledDensity。今日頭條選用dp或者pt作爲適配單位,給出的理由是項目中大部分都是使用dp做單位。
如果UI小姐姐給的設計圖是 360dp(寬) x 640dp(高) 的設計圖,如果要適配所有的屏幕,則 density = 設備屏幕的真實寬度(單位:px) / 360。這裏爲什麼會是這樣呢?上面公式中:px =dp * density,假定了所有的屏幕的寬度都是360dp,而px的值就是設備屏幕的真實寬度,所以就有了:density = 設備屏幕的真實寬度(單位:px) / 360。這樣1dp在所有屏幕的寬中所佔的比率都是一樣的,都是1/360,在xml中就可以按照設計圖來定義了。
而對於字體,Google推薦設置的單位爲sp,在上面的源碼中,字體的轉換公式爲:px= value * metrics.scaledDensity,這裏的value 是在xml中定義的以sp爲單位的值。一般情況下,scaledDensity 的值與 density 是相等的。但是如果在系統中設置了改變了字體的大小,scaledDensity 的值與 density 就不相等了。scaledDensity = 人爲修改的density * (系統的ScaledDensity / 系統的Density)。如果不需要字體大小隨系統設置而改變,就直接使用dp做單位好了.
/**
* 使用 dp 作爲適配單位(適合在新項目中使用,在老項目中使用會對原來既有的 dp 值產生影響)
* <br>
* <ul>
* dp 與 px 之間的換算:
* <li> px = density * dp </li>
* <li> density = dpi / 160 </li>
* <li> px = dp * (dpi / 160) </li>
* </ul>
*
* @param context
* @param designSize 設計圖的寬/高(單位: dp)
* @param base 適配基準
*/
private static void matchByDP(@NonNull final Context context, final float designSize, int base) {
final float targetDensity;
if (base == MATCH_BASE_WIDTH) {
targetDensity = sMatchInfo.getScreenWidth() * 1f / designSize;
} else if (base == MATCH_BASE_HEIGHT) {
targetDensity = sMatchInfo.getScreenHeight() * 1f / designSize;
} else {
targetDensity = sMatchInfo.getScreenWidth() * 1f / designSize;
}
final int targetDensityDpi = (int) (targetDensity * 160);
final float targetScaledDensity = targetDensity * (sMatchInfo.getAppScaledDensity() / sMatchInfo.getAppDensity());
final DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
displayMetrics.density = targetDensity;
displayMetrics.densityDpi = targetDensityDpi;
displayMetrics.scaledDensity = targetScaledDensity;
}
從這兩次的效果圖,可以很明顯的看出加了適配方案後,兩個屏幕中控件的大小差不多一致了。此套方案對於原來的老項目不太友好,因爲改了屏幕的density,所有佈局的實際尺寸都會發生變化,整個佈局中的所有尺寸都要進行修改一遍。
ps:以上被整理成了一個單獨適配屏幕的類,下載地址是:https://download.csdn.net/download/and_caicai/12412196