众所周知,Android 设备厂商非常多,各种尺寸的 Android 手机、平板层出不穷,屏幕分辨率也各有出入。

Google 为了解决分辨率过多的问题,在 Android 开发文档中定义了多种衡量尺寸的单位,方便开发者适配不同分辨率的 Android 设备。

尺寸单位

in

Inches,不难理解,就是英寸,基于屏幕的物理尺寸。1in=2.54cm。

mm

Millimeters,也就是毫米,基于屏幕的物理尺寸。不用多解释。

px

Pixels,即像素。我们用放大镜看显示屏,发现图像是由一个个小点组成的,这些小点就是像素点。

像素构成了完整图像

像素是图像的基本采样单位,它不是一个准确的物理量,因为像素点的物理大小是不确定的,像素也不是一个具体的点或者小方块(尽管可以用点和小方块来呈现),而是一个抽象的概念。

我们常说的手机分辨率就是指屏幕像素的数量,这就很好理解它的物理大小不确定的这句话了,比如一个屏幕比例为 16:9 的 5.5-inch 的手机,它的分辨率既可以是 1280×720 像素,也可以是 1920×1080 像素,也就是我们常说的 720P 和 1080P。

尺寸相同但像素密度不同的两个设备

也正因为如此,px 单位并不被建议使用,因为同样像素大小的图片在不同手机显示的实际大小可能不同。

当然,你可以用在一些线条的绘制中,比如需要画 1 像素表格线或阴影线的时候。

dp / dip

Density-independent Pixels,它是一种基于屏幕密度的抽象单位,密度无关像素,在不同的像素密度的设备上会自动适配。

虽然有 dpdip 两种说法,实际上是同一个概念,为了和 sp 更加一致,dp 会更加常用。

要理解 dp,首先要先引入 DPI 和 PPI 这两个概念。

DPI 全称是 Dots Per Inch,即网点密度,在纸质媒介时代,我们常用网点密度来描述印刷品的打印精度。DPI 常用于“设备参数”描述(如扫描仪和打印机),例如我设置了打印的分辨率为 96DPI,那么打印机在打印过程中,每英寸(Inch)的长度打印了 96 个点(Dot),打印机在每英寸内打印的墨点数越多,图片看起来越精细。这种概念也带入到 PC 时代的 Windows 系统,Windows 系统的默认 DPI 为 96。

因此,在 Android 上你可以将其理解为对角线每英寸的像素点的个数,计算公式如下:

DPI=height2+width2sizeDPI=\frac{\sqrt{height^{2}+width^{2}}}{size}

其中,heightweight 表示长宽的像素,平方和再开方其实就是勾股定理,得出对角线的像素个数,size 表示屏幕的尺寸,也就是我们常说的英寸,比如我们说这个手机是 5.5-inch 的,那么它的 size 在这里就是 5.5。

因此,屏幕同样大小的手机,分辨率越高,DPI 越高;分辨率相同,屏幕越小,DPI 越高。

前面也说了,DPI 其实面向的是印刷领域,而 PPI 面向的是计算机领域,PPI 全称是 Pixels Per Inch,即像素密度。在 Android 开发中 Drawable 文件对应的是DPI,实际上 PPI 和 DPI 的值是一样的,只是描述不同。

dp 表示的是在每英寸 160 点,即 160PPI 的屏幕上,和 px 相同长度的单位,计算公式如下:

px=dpPPI160px = dp \cdot \frac{PPI}{160}

  • 在 320×480 分辨率,像素密度为 160 时,1dp=1px。
  • 在 480×800 分辨率,像素密度为 240 时,1dp=1.5px。

为什么规定 160DPI 规格的显示器上,1dp=1px?

这个在官方文档中有给出解释,原因是因为第一款 Android 设备也就是 HTC T-Mobile G1 是属于 160DPI 的,事实上这也是 Android 1.6 之前唯一支持的屏幕配置。

HTC T-Mobile G1 准确的 DPI 并不等于160,它是 3.2-inch,320×480 分辨率,不难计算出它约为 180DPI,那为什么不使用 180DPI 作基准呢?

原因是 180DPI 不好作适配,Android 其实为了不至于为每一个设备制造商做适配,将不同屏幕大小和不同 DPI 的设备大致划分为四类:

设备分类

当然,随着设备屏幕越来越大,现在也增加了「xxhdpi」和「xxxhdpi」等,不过没关系,我们在这里只是讨论它的起源,大家可以看到 HTC T-Mobile G1 是属于「mdpi」区域的,所以也就是使用 160DPI 作基准的原因。

我们大致可以得出以下规格:

密度 密度值 代表分辨率
ldpi 120 240×320
mdpi 160 320×480
hdpi 240 480×800
xhdpi 320 720×1280
xxhdpi 480 1080×1920

不难看出,屏幕的密度值比 ldpi : mdpi : hdpi : xhdpi : xxhdpi = 0.75 : 1 : 1.5 : 2 : 3;即在「xhdpi」的密度下,1dp=2px;在「hdpi」情况下,1dp=1.5px,其他类推。

sp

Scale-independent Pixels,它是 Android 中专门用来描述字体大小的单位,一般情况下,sp 的值和 dp 的值是相同的,那为什么要特地用一个新的单位来描述字体呢?

如果你家里有老人家,或者你尝试教老年人使用智能手机时,你常常会先帮他们把字体调大,否则老人家可能会由于年纪大视力不好而看不清手机屏幕上的字。

而设置字体单位为 sp 的目的就是为了当用户调整系统设置的字体大小时,应用的字体也能够作出相应的改变。

也就是说,系统默认设置的 spdp 的大小值一致,而当用户把字体调大或调小后,这个 sp 则跟 dp 的值不一致了。

为什么 dp 不会像 sp 一样变化,这就是这两者应用对象的区别,sp 专门用于衡量字体,dp 则用来衡量控件以及间距的尺寸。那么假设有一张图片刚好填充整个屏幕,假如 dp 也像 sp 一样变化,把它调小了之后,它应该如何去显示,图片和屏幕边缘之间的空间应该由什么来填充?很不合理对吧。

pt

Points,点,也是基于屏幕的物理尺寸,印刷行业常用单位,等于 1/72 英寸。

计算公式如下:

1pt=(DPI/72)px1pt=(DPI/72)px

当『Photoshop』中新建画布的分辨率为 72DPI时,1pt=1px;当新建画布分辨率为 144DPI 时,1pt=2px。

在开发中的使用

为了更好的适配不同的屏幕,应该选择合理的单位,最常使用的,当然就是 dpsp,其他几个单位虽然用得少,但既然提供了,就当然有对应的使用场景,所以应该根据当前项目作选择。

要在像素密度不同的设备上提供良好的图形质量,应该以相应的分辨率在应用中提供每个位图的多个版本(针对每个密度级别提供一个版本)。否则,Android 系统必须缩放位图,使其在每个屏幕上占据相同的可见空间,从而导致缩放失真模糊。

不同密度大小的位图的相对尺寸

跟屏幕的密度比是一致的。之后,每当您引用该位图资源时,系统都会根据屏幕的 DPI 选择适当的位图。如果您没有为某个密度提供特定于密度的资源,那么系统会选取下一个最佳匹配项并对其进行缩放以适合屏幕。

如果您有一些系统绝不能缩放(或许是因为您在运行时自行对图片进行一些调整)的可绘制资源,则应将这些资源放在带有「nodpi」配置限定符的目录中。带有此限定符的资源被视为与密度无关,系统将不会对它们进行缩放。

除了创建多个特定于密度的图片版本之外,另一种方法是仅创建一个矢量图形。在借助矢量图形创建图片时,使用 XML 定义路径和颜色,而不是使用像素位图。因此,矢量图形可以缩放到任何尺寸而不会出现缩放失真,不过它们通常最适合图标等插图,而不太适合照片。

矢量图形通常以 SVG(可缩放矢量图形)文件的形式提供,但 Android 不支持此格式,因此必须将 SVG 文件转换为 Android 的矢量图格式。『Android Studio』中提供的『Vector Asset Studio』可以方便地进行转换。

另外,在合适的情况下使用矢量图形来代替位图也可以很好地达到减少安装包体积的效果。