目录
- 1 一些基本概念
- 2 传统标准适配难题
- 3 基于宽度的屏幕适配原则
1 一些基本概念
- ppi:pixels per inch,即物理设备每英寸的像素数。计算公式为:
ppi = sqrt(width² + height²) / 屏幕尺寸(inch)
为什么一定要计算出屏幕斜线的像素数呢?因为手机的屏幕尺寸指的就是手机屏幕斜线的长度(单位:英寸)。所以,理论上来说,如果知道屏幕宽度的尺寸,也可以直接通过 ppi = width / 宽度尺寸(inch)
计算出来。
dpi:dot per inch,即每英寸的点数。这个单位原本用于印刷行业,描述的是打印机的打印精度,即打印机在印刷纸上每英寸范围内可以均匀印射出多少个颜色点,dpi 越高说明打印机的打印精度越高,效果越细腻。安卓借用了 dpi 的概念来表示屏幕的像素密度,但根据 ppi 的概念我们知道 ppi 可以更直观地表征屏幕像素密度,那为什么安卓要借用印刷行业的 dpi 概念来重新表示屏幕密度呢?找遍了谷歌文档,并没有一个官方正式的解释。一方面,在实践中我们发现,dpi 是约等于实际的 ppi 的;另一方面,对于不同的屏幕密度需要提供不同分辨率的图片资源,而为了让设备能够加载正确的图片资源,理想状态下就需要对每一种具体的屏幕密度都提供一张不同分辨率的图片,这样做显然不现实,所以安卓对屏幕密度做了一个标准分类,如:ldpi(低密度≈120dpi)、mdpi(正常密度≈160dpi)、hdpi(高密度≈240dpi)、xhdpi(超高密度≈320dpi)、xxhdpi(超超高密度≈80dpi)、xxxhdpi(超超超高密度≈640dpi)。每一个设备都需要划分到这些标准分类中的一类中,一种比较简单的做法就是,直接将其 dpi 设置成谷歌推荐的几个值(120/160/240/320/480/640)之一,规则就是选择与其实际 ppi 最接近的那一个,之所以不直接设置成 ppi,我觉得很可能是根据这几个标准值最终计算出来的 density(后面会介绍)值更加易于计算。总之,实际运用中我们可以粗略地将 dpi 表征为物理屏幕的像素密度。
dip/dp:density-independent pixels,即密度无关像素。如前所述,dpi 表征了屏幕的像素密度。如果在一个 160dpi 的屏幕上和一个 320dpi 的屏幕上展现一个用户看起来大小一样的矩形,那么实际上这个一样的大小指的并不是其像素点的多少一样,而是长宽的物理尺寸(单位为英寸或者厘米)相同。所以我们在进行开发的时候,需要一种单位来衡量在不同像素密度的设备上相同大小的界面元素。理论上可以直接用英寸或者厘米或者毫米,但是这些单位因为与像素之间没有直接关联,所以在渲染的时候无法转换为最终的像素点数。为此,安卓发明了 dp 这样一个单位。其原理是以 160dpi 的设备作为所有其它设备的参考系,规定 160dpi 的设备中,一个 px 等于一个 dp,并将其它所有设备的 每一个英寸 都等分为 160 份,每一份即为该设备的一个 dp。比如,320dpi 的设备,其一个英寸等分为 160 份后,每一份由 2 个像素点组成,所以其一个 dp 表示 2 个 px。其它设备以此类推。于是我们找到了一种既可以表征屏幕物理尺寸(英寸/厘米/毫米),又可以转换为像素值(px)的单位——dp。
density:理解了 dpi 和 dp 的概念后,density 的概念也就很容易理解了。density 我将其理解为 pixels per dp,即单位 dp 里的像素数。其计算公式为:
density = dpi / 160 (一个英寸等分为 160 份后,每一份的像素数即为 density)
这样,dp,px,density 之间的关系为:
dp = px / density = px / (dpi / 160)
各标准屏幕密度下的 density 值如下:
密度 | ldpi(120dpi) | mdpi(160dpi) | hdpi(240dpi) | xhdpi(320dpi) | xxhdpi(480dpi) | xxxhdpi(640dpi) |
---|---|---|---|---|---|---|
density | 0.75 | 1 | 1.5 | 2 | 3 | 4 |
2 传统标准适配难题
如果直接按照官方的 density 标准,所有设备的宽高尺寸就都可以从 px 单位转换为 dp 单位。比如分辨率为 1080×1920px,480dpi 的设备一(density=3),其 dp 尺寸变为 360×640dp;而分辨率为 1440×2880px,560dpi 的设备二(density=3.5),其 dp 尺寸变为 411×823dp。如果我们在两个设备中放一个 60×60dp 的图片,那么此时该图像占设备一宽度的 16.66%,而只占设备二宽度的 14.59%。
显然,设备二是一种相对宽屏的设备,从设计初衷上来讲是为了在一行容纳更多的界面元素。如果设备的尺寸差异并不太大,那么适配难度是可以接受的,但是随着安卓碎片化的加剧,其适配的复杂性也越来越大。这是因为,当按照一种设备尺寸给出设计图后,用官方的 density 标准设置界面元素的 dp 尺寸后,该元素在不同设备上占据的比例都是不一样的,所以其剩余可容纳的空间也不一。比如我们以中等宽度的屏幕作为设计图把一行填满了,那么在布局到更宽屏的设备上时可能没有问题,但是布局到更窄屏的设备上时,就会发生行溢出,这时就得考虑溢出和不溢出的时候如何布局,或者手动调整为比例尺寸。
为了适应安卓碎片化的事实,现在业界推崇一种基于宽度的屏幕适配方案,以牺牲理想适配原则为代价,换取开发成本的大大降低。
3 基于宽度的屏幕适配原则
基于宽度的屏幕适配原则如下:
将所有的手机设备的屏幕宽度都按照相同的份数进行等分。
没看错,原则就是这么简单!具体来说,假设等分的份数为 N(即设计稿给出的屏幕宽度),任意设备的宽度为 W(px),于是重新定义该设备的 density 为:
density = W / N
这实际上也重新定义了 dp,即所有设备的宽度都为 Ndp。于是我们给定一张正方形的图片,我们要以 60×60dp 来显示,那么在任何设备上,该图片占据设备宽度的的比例都为 60/N。
默认情况下,所有设备的单位尺寸都等分为 160 份,这样每种设备的宽度因为英寸数不一样,最终等分的份数也不一样。在该种方案中,我们将设备的宽度都等分为一样的份数,这样每个界面元素的尺寸都按照这个 份(W/N) 为单位来进行衡量,那么其在不同设备中所占的屏宽比例都是一样的。
比如,假定 N=375,
设备一为 1080×1920px,其 density=1080/375=2.88,利用公式 px = dp × density
,60×60dp 的图片实际占据的像素尺寸为 172.8×172.8px。
设备二为 1440×2880px,其 density=1440/375=3.84,60×60dp 的图片实际占据的像素尺寸为 230.4×230.4px。
但是图片在两个设备中占据的屏幕宽度比例都为 172.8/1080=230.4/1440=60/375=16%。
目前比较流行的方案有 今日头条方案 和 SmallestWidth 限定符方案。它们都是遵循的以上原则,只不过在具体实现上有差异。
今日头条方案的实现原理是直接修改系统的 density 和 dpi 来达到全局适配的目的,简单而高效。
而 SmallestWidth 限定符方案是通过生成大量的 values-sw(XX)dp 限定文件夹来尽可能覆盖大部分机型尺寸,然后在各个限定文件夹的 dimens.xml 文件中批量写入对应的虚拟 dp 值来实现适配。比如设计稿以 375 宽度为基准(即 N=375),在 values-sw360dp/dimens.xml 中,其一个虚拟 dp 值为:vdp_1 = 360/375 = 0.96dp
,这样我们在布局文件中原本应该设置 60dp 的地方,改为设置 @dimen/vdp_60(即57.6dp) 即可,于是其实际像素值为 57.6×3=172.8px(vdp_60×系统默认density)。即,虚拟 dp 的计算公式为:
vdp = dp × W / N = dp × density
实际上在今日头条方案中,修正后的 density(vdensity)即为:
vdensity = W / N
目前来看,基于宽度的适配方案在前端开发中也成为业界主流方案,因为前端开发也面临和客户端开发一样的屏幕适配难题。这种方案可以极大的减轻程序员业务开发的负担,减少开发成本,易于维护,而且理想型的适配相比这种折中方案来讲在用户体验上并不具有明显的市场优势,所以也可以说这是市场选择的结果。
关于前端开发中的宽度适配方案,可以参考 用rem实现移动设备页面元素适配
加入技术交流群
准备建立一个技术交流群,大家可以讨论技术、内推工作、互相帮助。因为二维码容易失效,个人微信号加太多容易被封,所以请大家先关注公众号——小舍,然后在公众号给我发送消息,我拉大家入群。