在学习 Android UI 开发的初期,经常被一些常用概念如 dp、sp 和它们与 px 的换算等虐,要避免被虐,最好的方法当然是知其所以然,再见到它们就胸中有料心不慌了。
参考 http://developer.android.com/guide/practices/screens_support.html#terms
参考 http://developer.android.com/guide/topics/resources/more-resources.html#Dimension
dp 转 px
参考http://developer.android.com/guide/practices/screens_support.html#dips-pels
为了简单起见,Android 将屏幕密度概括为 6 种:
但是并非表示所有 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