在学习 Android UI 开发的初期,经常被一些常用概念如 dp、sp 和它们与 px 的换算等虐,要避免被虐,最好的方法当然是知其所以然,再见到它们就胸中有料心不慌了。
背景知识
参考 http://developer.android.com/guide/practices/screens_support.html#terms
-
屏幕大小(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 就是屏幕长和宽上的像素个数。
尺寸
参考 http://developer.android.com/guide/topics/resources/more-resources.html#Dimension
-
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
参考http://developer.android.com/guide/practices/screens_support.html#dips-pels
为了简单起见,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
在http://developer.android.com/reference/android/util/DisplayMetrics.html#scaledDensity中可以看到scaledDensity
就是控制字体尺寸的缩放因子。于是猜想 sp 数 x 换算成 px 数 y 的公式:
y = x * scaledDensity
这里的 scaledDensity 获取方式为getResources().getDisplayMetrics().scaledDensity
。
这个猜想是否正确呢?看看下面一张图就明白啦!(Nexus 4 模拟器截图)
根据分辨率和屏幕密度求屏幕尺寸
使用 adb 命令 adb shell wm size
可以得到设备分辨率,adb shell wm density
可以得到设备屏幕密度,但貌似没有办法直接得到屏幕尺寸,只能拿到这两个数据之后换算。
公式:
sqrt(sqr(width)+sqr(height))/density