为什么要屏幕适配
Android 开发过程中我们常用的尺寸单位有 px,dp, 还有一种 sp 一般是用于字体的大小. 但是由于 px 是像素单位, 比如我们通常说的手机分辨例如 1920*1080 都是 px 的单位. 现在 Android 屏幕分辨率碎片化 720x1280,1080x1920,2280x1080, 这就造成例如 187px 会在各个分辨率的机型上都是显示一样大小的, 那肯定不是我们想要的效果, 所以用 px 单位我们是难以达到适配效果的, 那么为什么用 dp 可以呢?
使用 px 单位从左到右依次为 480 800,1080 1920,1440 * 2560
使用 dp 单位从左到右依次为 480 800,1080 1920,1440 * 2560
屏幕总宽度依次为 320dp,415dp,411dp
那么什么是 dp?
dp 指的是设备独立像素, 以 dp 为尺寸单位的控件, 在不同分辨率和尺寸的手机上代表了不同的真实像素, 比如在分辨率较低的手机中, 可能 1dp=1px, 而在分辨率较高的手机中, 可能 1dp=2px, 这样的话, 一个 187dp 高度的控件, 在不同的手机中就能表现出差不多的大小了.
dp 如何计算成 px
Android 中的 dp 在渲染前会将 dp 转为 px, 计算公式:
- px = density * dp;
- density = dpi / 160;
- px = dp * (dpi / 160);
而 dpi 是根据屏幕真实的分辨率和尺寸来计算的, 每个设备都可能不一样的.
由于 density 不是固定不变的, 所以每个分辨率不同的设备他们的 density 都肯定不相等, 这样就会造成每个设备的宽 / 高对应的总 dp 都是不同的, 假设 480 800 分辨率的 density 是 1.5,1080 1920 分辨率的 density 是 2.6,1440 * 2560 分辨率的 density 是 3.5. 那么它们对应的宽度总 dp = (宽度 px) / density, 分别为 320dp,415dp,411dp. 可以看出单位为 dp 的时候三个设备之间的差距就不是很大了, 但是这样肯定还是不能满足我们对屏幕适配的要求的. 下面来看看 Android 常见的三种比较成熟的屏幕适配方案, 并分析这几种方案的优劣.
屏幕适配方案
1.1 宽高限定符适配
设定一个基准的分辨率, 也就是设计图对应的分辨率, 其他分辨率都根据这个基准分辨率来计算, 在不同的尺寸文件夹内部, 根据该尺寸编写对应的 dimens 文件.
比如我们的设计图 375 * 667 为基准分辨率
宽度为 375, 将任何分辨率的宽度整分为 375 份, 取值为 x1-x375
高度为 667, 将任何分辨率的高度整分为 667 份, 取值为 y1-y667
那么对于 1080*1920 的分辨率的 dimens 文件来说,
- x1=(1080/375)*1=2.88px
- x2=(1080/375)*2=5.76px
- y1=(1920/667)*1=2.87px
- y2=(1920/667)*2=5.75px
当代码里面引用高度为 y_187, 在 App 运行时会根据当前设备分辨率去找对应 xml 文件中对应的高度, 我们就可以按照设计稿上的尺寸填写相对应的 dimens 引用了, 这样基本解决了我们的适配问题, 而且极大的提升了我们 UI 开发的效率.
验证方案
简单通过计算验证下这种方案是否能达到适配的效果, 例如设计图上有一个宽 187dp 的 View.
480 * 800
设计图占宽比: 187dp / 375dp = 0.498
实际在 480 800 占宽比 = 187 1.28px / 480 = 0.498
1080 * 1920
设计图占宽比: 187dp / 375dp = 0.498
实际在 1080 1920 占宽比 = 187 2.88px / 1080 = 0.498
计算高同理
但是这个方案有一个致命的缺陷, 那就是需要精准命中才能适配, 比如 1920x1080 的手机就一定要找到 1920x1080 的限定符, 否则就只能用统一的默认的 dimens 文件了. 而使用默认的尺寸的话, UI 就很可能变形, 简单说, 就是容错机制很差.
1.2 smallestWidth 适配
smallestWidth 适配, 或者叫 sw 限定符适配. 指的是 Android 会识别屏幕可用高度和宽度的最小尺寸的 dp 值 (其实就是手机的宽度值), 然后根据识别到的结果去资源文件中寻找对应限定符的文件夹下的资源文件.
这种机制和上文提到的宽高限定符适配原理上是一样的, 都是系统通过特定的规则来选择对应的文件.
可以把 smallestWidth 限定符屏幕适配方案 当成这种方案的升级版, smallestWidth 限定符屏幕适配方案 只是把 dimens.xml 文件中的值从 px 换成了 dp, 原理和使用方式都是没变的
├── src/main
│ ├── res
│ ├── ├──values
│ ├── ├──values-sw320dp
│ ├── ├──values-sw360dp
│ ├── ├──values-sw400dp
│ ├── ├──values-sw411dp
│ ├── ├──values-sw480dp
│ ├── ├──...
│ ├── ├──values-sw600dp
│ ├── ├──values-sw640dp
验证方案
1920 * 1080 分辨率的手机, dpi 为 420, 我们同样设置一个 View 为 187dp 宽
density = (dpi = 420) / 160 = 2.6
屏幕总宽度 dp = 1080 / density = 415
找到文件夹 values-sw410dp 下的 187dp = 204.45dp
通过公式 px = density * dp, 计算出 px = 531.57
算出占屏幕宽度的比例, 56.86 / 1080 = 0.492
1440 * 2560 分辨率的手机, dpi 为 560, 我们同样设置一个 View 为 187dp 宽
density = (dpi = 420) / 160 = 3.5
屏幕总宽度 dp = 1440 / density = 411
找到文件夹 values-sw410dp 下的 187dp = 204.45dp
通过公式 px = density * dp, 计算出 px = 715.57
算出占屏幕宽度的比例, 715.57 / 1440 = 0.496
因为识别的文件夹是 values-sw410dp 的文件夹, 但是屏幕宽度为 415dp 和 411dp, 所以最后计算出的占比会有一点点误差, 基本可以忽略不计, 可以达到相对比较准确的适配效果
优点
非常稳定, 极低概率出现意外
不会有任何性能的损耗
适配范围可自由控制, 不会影响其他三方库
在插件的配合下, 学习成本低
缺点
侵入性高, 在所有地方都需要引用.
还是没有办法覆盖所有的机型分辨率, 部分机型可能适配效果还是不佳
不能以高度为基准进行适配
生成很多文件, 增大 App 体积 1~2M
1.3 今日头条适配方案
今日头条屏幕适配方案的核心原理在于, 根据以下公式算出 density
默认 px = density * dp, 也就是屏幕总宽度 dp = 屏幕宽度 px / density, 这个时候我们假设所有设备上的屏幕总宽度 dp 会等于我们设计图 375dp, 那么可以得出一个公式:
density = 屏幕宽度 px / 设计图宽度 (375dp)
然后我们通过系统 API, 将 density 赋值给系统, 抛弃掉系统默认计算 density 的计算公式.
这样可以很巧妙的实现屏幕适配, 而且侵入性极低, 甚至可以忽略不计.
验证方案
1920 * 1080 分辨率的手机, 我们同样设置一个 View 为 187dp 宽, 设计图宽度为 375dp
density = (屏幕宽度 px = 1080) / 375 = 2.88
View 宽度 = density * 187dp = 538.56
算出占屏幕宽度的比例, 57.6 / 1080 = 0.498
1440 * 2560 分辨率的手机, 我们同样设置一个 View 为 187dp 宽, 设计图宽度为 375dp
density = (屏幕宽度 px = 1440) / 375 =3.84
View 宽度 = density * 187dp = 718.08
算出占屏幕宽度的比例, 718.08 / 1440 = 0.498
可以看出, 这种方案是完全没有误差的, 而且侵入性极低, 只需要修改系统的 density. 虽然修改系统的 density 属性会产生一小部分影响, 但是基本都是很好解决的.
优点
使用成本非常低, 操作非常简单
侵入性非常低
可适配三方库的控件和系统的控件
缺点
会全局影响 App 的控件大小, 例如一些第三方库控件, 他们设计的时候可能设计图尺寸并不是像我们一样是 375dp, 这样就会导致控件大小变形等一些问题.
参考文章
骚年你的屏幕适配方式该升级了!-SmallestWidth 限定符适配方案 https://juejin.im/post/5ba197e46fb9a05d0b142c62
Android 屏幕适配终结者
Android 目前最稳定和高效的 UI 适配方案 https://www.jianshu.com/p/a4b8e4c5d9b0
广而告之
来源: https://segmentfault.com/a/1190000019089019