在學習 Android UI 開發的初期,經常被一些常用概念如 dp、sp 和它們與 px 的換算等虐,要避免被虐,最好的方法當然是知其是以然,再見到它們就胸中有料心不慌了。
背景知識螢幕大小(Screen size)
螢幕對角線的實際實體大小。通常以英寸(inch)為機關。
螢幕密度(Screen density)
每英寸上的像素個數。通常被稱作多少 dpi(dots per inch)或多少 ppi(pixels per inch)。
比如,LG Nexus 5 的螢幕尺寸為 4.95 英寸,分辨率為 1920*1080,則它的對角線上的像素數為 sqrt((sqr(1920)+sqr(1080)),是以它的螢幕密度為 sqrt((sqr(1920)+sqr(1080))/4.95=445.03,約為 445dpi。
分辨率(Resolution)
螢幕上的實體像素個數。
比如 LG Nexus 5 的分辨率為 1920*1080,這裡的 1920 和 1080 就是螢幕長和寬上的像素個數。
尺寸dp(Density-independent Pixels)
在不同大小、密度和分辨率的螢幕上的實體大小都近似相等的虛拟尺寸機關。
約為 1/160 英寸(為什麼是約為?稍後講解)。
sp(Scale-independent Pixels)
基于首選字型大小的縮放像素。
與 dp 類似,但是會根據使用者的首選字型大小縮放。
pt(Points)
1/72 英寸。
px(Pixels)
像素。
mm(Millimeters)
毫米。
in(Inches)
英寸,約 2.539999918 厘米。
換算
dp 轉 px
為了簡單起見,Android 将螢幕密度概括為 6 種:ldpi(low) ~120dpi
mdpi(medium) ~160dpi
hdpi(high) ~240dpi
xhdpi(extra-high) ~320dpi
xxhdpi(extra-extra-high) ~480dpi
xxxhdpi(extra-extra-extra-high) ~640dpi
但是并非表示所有 Android 手機隻有這幾個螢幕密度,比如上面舉例的 LG Nexus 5 的螢幕密度是 445dpi,近似地歸于 xxhdpi,Android 在内部進行 dp 到 px 的換算時将采用 480dpi 而非 445dpi。
簡而言之,dp 數 x 換算成 px 數 y 的公式:
// 向上取整
y = x * generalizedDensity / 160
這裡的 generalizedDensity 就是以上 6 種中的一個值,比如 LG Nexus 5 的實際螢幕密度為 445dpi,但是歸于 xxhdpi,是以它的 generalizedDensity 就是 480dpi。
官方文檔給出的轉換 dp 到 px 的代碼示例為:
// The gesture threshold expressed in dp
private static final float GESTURE_THRESHOLD_DP = 16.0f;
// Get the screen's density scale
final float scale = getResources().getDisplayMetrics().density;
// Convert the dps to pixels, based on density scale
mGestureThreshold = (int) (GESTURE_THRESHOLD_DP * scale + 0.5f);
// Use mGestureThreshold as a distance in pixels...
在 mdpi(160dpi)上 1dp=1px(還記得前面講過的 1dp 約為 1/160 英寸嗎?在 160dpi 的螢幕上,1px=1/160 英寸),這裡的getResources().getDisplayMetrics().density實際上就等于我們的generalizedDensity / 160,表示将 dp 轉換為 px 的一個轉換因子。
然後來了解一下為何 dp 是約為 1/160 英寸。
還是以 LG Nexus 5 舉例,比如 160dp,若在一個螢幕密度恰好是 480dpi 的機器上,那它會是準确的 1 英寸,但是 LG Nexus 5 的螢幕密度是 445dpi,根據上面的公式計算得出:160dp = 160 * 480 / 160 px = 480px,則它的實際顯示尺寸為 480/445=1.07865 英寸。是以原因是dp 換算成 px 是使用 Android 概括的六種螢幕密度之一,而非實際螢幕密度,是以在不同的手機上相同數量的 dp 顯示尺寸會有輕微差異。
sp 轉 px
y = x * scaledDensity
這裡的 scaledDensity 擷取方式為getResources().getDisplayMetrics().scaledDensity。
這個猜想是否正确呢?看看下面一張圖就明白啦!(Nexus 4 模拟器截圖)

根據分辨率和螢幕密度求螢幕尺寸
使用 adb 指令 adb shell wm size 可以得到裝置分辨率,adb shell wm density 可以得到裝置螢幕密度,但貌似沒有辦法直接得到螢幕尺寸,隻能拿到這兩個資料之後換算。
公式:
sqrt(sqr(width)+sqr(height))/density