huanzhiyazi / articles

我的原创文章,包括且不限于技术 blog,历史,文学写作,日常心得等
103 stars 32 forks source link

技术 / android / 基于宽度的屏幕适配方案 #12

Open huanzhiyazi opened 4 years ago

huanzhiyazi commented 4 years ago

目录



1 一些基本概念[Top]

ppi = sqrt(width² + height²) / 屏幕尺寸(inch)

为什么一定要计算出屏幕斜线的像素数呢?因为手机的屏幕尺寸指的就是手机屏幕斜线的长度(单位:英寸)。所以,理论上来说,如果知道屏幕宽度的尺寸,也可以直接通过 ppi = width / 宽度尺寸(inch) 计算出来。

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 传统标准适配难题[Top]

如果直接按照官方的 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 基于宽度的屏幕适配原则[Top]

基于宽度的屏幕适配原则如下:

将所有的手机设备的屏幕宽度都按照相同的份数进行等分。

没看错,原则就是这么简单!具体来说,假设等分的份数为 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实现移动设备页面元素适配

star-du commented 7 months ago

讲解的很明晰!个人小结:

另外补充:文档提到,对于文字尺寸,推荐使用 sp (scalable pixels) 作为单位,以尊重用户的字体大小设置。

附文档链接:Support different pixel densities  |  Android Developers

欢迎指正!

huanzhiyazi commented 7 months ago

讲解的很明晰!个人小结:

  • dp 是尺寸大小的参考单位,可以在不考虑屏幕密度影响下衡量图形的视觉大小
  • dpi 是像素密度单位,可以衡量通俗说的清晰度/解析度。它的 baseline 是 160 dpi
  • px 是实际显示的像素。原理上, px = dp * (dpi / 160)。而实际使用时,根据 Android 文档,应当用 TypedValue.applyDimension 之类的方法计算
  • “基于宽度的适配”背后的思想是:根据屏幕尺寸,把设计稿等比缩放,尽可能使图形占屏幕的比例不变。这和前端基于 rem 的适配思路类似

另外补充:文档提到,对于文字尺寸,推荐使用 sp (scalable pixels) 作为单位,以尊重用户的字体大小设置。

附文档链接:Support different pixel densities  |  Android Developers

欢迎指正!

@star-du 总结的非常棒! You'r super star! ^^