Android UI 开发里的尺寸单位理解
Pin Young Lv9

在学习 Android UI 开发的初期,经常被一些常用概念如 dp、sp 和它们与 px 的换算等虐,要避免被虐,最好的方法当然是知其所以然,再见到它们就胸中有料心不慌了。

背景知识

参考 http://developer.android.com/guide/practices/screens_support.html#terms

  1. 屏幕大小(Screen size)

    屏幕对角线的实际物理大小。通常以英寸(inch)为单位。

  2. 屏幕密度(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。

  3. 分辨率(Resolution)

    屏幕上的物理像素个数。

    比如 LG Nexus 5 的分辨率为 1920*1080,这里的 1920 和 1080 就是屏幕长和宽上的像素个数。

尺寸

参考 http://developer.android.com/guide/topics/resources/more-resources.html#Dimension

  1. dp(Density-independent Pixels)

    在不同大小、密度和分辨率的屏幕上的物理大小都近似相等的虚拟尺寸单位。

    约为 1/160 英寸(为什么是约为?稍后讲解)。

  2. sp(Scale-independent Pixels)

    基于首选字体大小的缩放像素。

    与 dp 类似,但是会根据用户的首选字体大小缩放。

  3. pt(Points)

    1/72 英寸。

  4. px(Pixels)

    像素。

  5. mm(Millimeters)

    毫米。

  6. 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 的公式:

1
2
// 向上取整
y = x * generalizedDensity / 160

这里的 generalizedDensity 就是以上 6 种中的一个值,比如 LG Nexus 5 的实际屏幕密度为 445dpi,但是归于 xxhdpi,所以它的 generalizedDensity 就是 480dpi。

官方文档给出的转换 dp 到 px 的代码示例为:

1
2
3
4
5
6
7
8
9
// 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 的公式:

1
y = x * scaledDensity

这里的 scaledDensity 获取方式为getResources().getDisplayMetrics().scaledDensity

这个猜想是否正确呢?看看下面一张图就明白啦!(Nexus 4 模拟器截图)

image

根据分辨率和屏幕密度求屏幕尺寸

使用 adb 命令 adb shell wm size 可以得到设备分辨率,adb shell wm density 可以得到设备屏幕密度,但貌似没有办法直接得到屏幕尺寸,只能拿到这两个数据之后换算。

公式:

1
sqrt(sqr(width)+sqr(height))/density