目前最主流的两种Android屏幕适配方案,今日头条适配方案

版权声明:转载必须注明本文转自郭子轩的博客 https://blog.csdn.net/gpf1320253667/article/details/84872376

在讲解两种主流屏幕适配方案之前,我们先来回顾下Android屏幕适配的较早期解决方案以及存在的问题。

最原始屏幕适配方案:dp尺寸值+wrap_content+线性布局权重百分比

大家都知道,在标识尺寸的时候,Android并不推荐我们使用px这个真实像素单位,因为不同的手机之间,分辨率是不同的,比如一个96 * 96像素的控件在分辨率越来越高的手机上会在整体UI中看起来越来越小。
在这里插入图片描述
出现类似于上图这样的情况,整体的布局效果可能会变形,所以px这个单位在布局文件中是不推荐的。
针对这种情况,Android推荐使用dp作为尺寸单位来适配UI.
dp指的是设备独立像素,以dp为尺寸单位的控件,在不同分辨率和尺寸的手机上代表了不同的真实像素,比如在分辨率较低的手机中,可能1dp=1px,而在分辨率较高的手机中,可能1dp=2px,这样的话,一个96 * 96dp的控件,在不同的手机中就能表现出差不多的大小了。
我们可以说,通过dp加上自适应布局和weight比例布局可以基本解决不同手机上适配的问题,这基本是最原始的Android适配方案。

存在的问题:
1,这只能保证我们写出来的界面适配绝大部分手机,部分手机仍然需要单独适配,因为并不是所有的1080P的手机dpi都是480,比如Google 的Pixel2(1920* 1080)的dpi就是420,也就是说,在Pixel2中,1dp=2.625px,这样会导致相同分辨率的手机中,这样,一个100dp* 100dp的控件,在一般的1080P手机上,可能都是300px,而Pixel 2 中 ,就只有262.5px,这样控件的实际大小会有所不同。
2,通过dp直接适配,我们只能让UI基本适配不同的手机,但是在设计图和UI代码之间的鸿沟,dp是无法解决的,当我们拿到设计稿的时候,设计稿的ImageView是128px* 128px,当我们在编写layout文件的时候,却不能直接写成128dp*128dp。在把设计稿向UI代码转换的过程中,我们需要耗费相当的精力去转换尺寸,这会极大的降低我们的生产力,拉低开发效率。

进阶方案:宽高限定符适配

简单说,就是穷举市面上所有的Android手机的宽高像素值,在res文件夹下创建各种屏幕分辨率对应的values-xxx文件夹,如下图:

├── src/main
│   ├── res
│   ├── ├──values
│   ├── ├──values-800x480
│   ├── ├──values-860x540
│   ├── ├──values-1024x600
│   ├── ├──values-1024x768
│   ├── ├──...
│   ├── ├──values-2560x1440

然后根据一个基准分辨率,来生成其他各种分辨率对应的dimens.xml文件。

比如以480x320为基准分辨率,那么:
1,宽度为320,将任何分辨率的宽度整分为320份,取值为x1-x320
2,高度为480,将任何分辨率的高度整分为480份,取值为y1-y480

那么对于800*480的分辨率的dimens文件来说:
x1=(480/320)*1=1.5px
x2=(480/320)*2=3px

在这里插入图片描述
这个时候,如果我们的UI设计界面使用的就是基准分辨率,那么我们就可以按照设计稿上的尺寸填写相对应的dimens引用了,而当APP运行在不同分辨率的手机中时,这些系统会根据这些dimens引用去该分辨率的文件夹下面寻找对应的值。这样基本解决了我们的适配问题,而且极大的提升了我们UI开发的效率。

假设设计图上一个控件的宽度为720px,那么布局中就可以这样定义:

android:layout_width="@dimens/x720"

当运行程序的时候,系统会根据当前设备的分辨率去寻找对应分辨率文件夹下对应的dimens.xml文件,例如运行在分辨率为1280x720的设备上,系统会自动找到对应的values-1280x720文件夹下的dimens.xml文件中的x720对应的值。

该方案的弊端:
但是这个方案有一个致命的缺陷,那就是需要精准命中才能适配,比如1920x1080的手机就一定要找到1920x1080的限定符,否则就只能用统一的默认的dimens文件了。而使用默认的尺寸的话,UI就很可能变形,简单说,就是容错机制很差。

基于该原理的开源框架:AutoLayoutActivity(已停止维护)
基于上面的原因该屏幕适配方案也逐渐退出历史舞台。

目前两种主流的屏幕适配方案,AutoLayoutActivity方案的替代者

1,smallestWidth最小宽度限定符适配方案

该方案可以看做是宽高限定符适配方案的升级版,只是将dimens.xml文件中的值从px换成了dp,基本原理和使用方式基本上是没有变的。

├── src/main
│   ├── res
│   ├── ├──values
│   ├── ├──values-sw320dp
│   ├── ├──values-sw360dp
│   ├── ├──values-sw400dp
│   ├── ├──values-sw411dp
│   ├── ├──values-sw480dp
│   ├── ├──...
│   ├── ├──values-sw600dp
│   ├── ├──values-sw640dp

原理
开发者现在项目中根据主流屏幕的最小宽度(smallestWidth)生成一些列的values-sw< N>dp等等的文件夹(包含各自的dimens.xml文件),当项目运行到设备上时,系统会根据当前设备屏幕的最小宽度去匹配对应的values-sw< N>dp文件夹,然后取该文件夹下dimens.xml文件中的值,如果系统根据当前设备的最小宽度没有找到对应的values-sw< N>dp文件夹,则会去寻找与之最小宽度相近的values-sw< N>dp文件夹,系统只会去寻找小于或等于当前设备最小宽度的values-sw< N>dp文件夹,这就是优于 宽高限定符屏幕适配方案 的容错率,并且也可以少生成很多 values-swdp 文件夹,减轻 App 的体积。

什么是 smallestWidth?
smallestWidth 翻译为中文的意思就是 最小宽度,那这个 最小宽度 是什么意思呢?
系统会根据当前设备屏幕的 最小宽度 来匹配 values-swdp,为什么不是根据 宽度 来匹配,而要加上 最小 这两个字呢?
这就要说到,移动设备都是允许屏幕可以旋转的,当屏幕旋转时,屏幕的高宽就会互换,加上 最小 这两个字,是因为这个方案是不区分屏幕方向的,它只会把屏幕的高度和宽度中值最小的一方认为是 最小宽度,这个 最小宽度 是根据屏幕来定的,是固定不变的,意思是不管您怎么旋转屏幕,只要这个屏幕的高度大于宽度,那系统就只会认定宽度的值为 最小宽度。

smallestWidth 的值是怎么算的?
要先算出当前设备的 smallestWidth 值我们才能知道当前设备该匹配哪个 values-swdp 文件夹。
我们假设设备的屏幕信息是 1920 * 1080、480 dpi
根据上面的规则我们要在屏幕的高度和宽度中选择值最小的一方作为最小宽度,1080 < 1920,明显 1080 px 就是我们要找的 最小宽度 的值,但 最小宽度 的单位是 dp,所以我们要把 px 转换为 dp。
px / density = dp,DPI / 160 = density,所以最终的公式是 px / (DPI / 160) = dp
所以我们得到的 最小宽度 的值是 360 dp (1080 / (480 / 160) = 360)
现在我们已经算出了当前设备的最小宽度是 360 dp,我们晓得系统会根据这个 最小宽度 帮助我们匹配到 values-sw360dp 文件夹下的 dimens.xml 文件,如果项目中没有 values-sw360dp 这个文件夹,系统才会去匹配相近的 values-swdp 文件夹。
接下来我们再来看看 values-sw360dp 文件夹中的这个 dimens.xml 是根据什么原理生成的。

dimens.xml 生成原理
说到 dimens.xml 的生成,就要涉及到两个因数,第一个因素是 最小宽度基准值,第二个因素就是您的项目需要适配哪些 最小宽度,通俗理解就是需要生成多少个 values-swdp 文件夹。

因素一
最小宽度基准值 是什么意思呢?简单理解就是您需要把设备的屏幕宽度分为多少份,假设我们现在把项目的 最小宽度基准值 定为 360,那这个方案就会理解为您想把所有设备的屏幕宽度都分为 360 份,方案会帮您在 dimens.xml 文件中生成 1 到 360 的 dimens 引用,比如 values-sw360dp 中的 dimens.xml 是长这样的:

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <dimen name="dp_1">1dp</dimen>
    <dimen name="dp_2">2dp</dimen>
    <dimen name="dp_3">3dp</dimen>
    <dimen name="dp_4">4dp</dimen>
    <dimen name="dp_5">5dp</dimen>
    <dimen name="dp_6">6dp</dimen>
    <dimen name="dp_7">7dp</dimen>
    <dimen name="dp_8">8dp</dimen>
    <dimen name="dp_9">9dp</dimen>
    <dimen name="dp_10">10dp</dimen>
    ...
    <dimen name="dp_356">356dp</dimen>
    <dimen name="dp_357">357dp</dimen>
    <dimen name="dp_358">358dp</dimen>
    <dimen name="dp_359">359dp</dimen>
    <dimen name="dp_360">360dp</dimen>
</resources>

values-sw360dp 指的是当前设备屏幕的 最小宽度 为 360dp,如果把屏幕宽度分为 360 份,刚好每份等于 1dp,所以每个引用都递增 1dp,值最大的 dimens 引用 dp_360 值也是 360dp,刚好覆盖屏幕宽度。
下面再来看看将 最小宽度基准值 定为 360 时,values-sw400dp 中的 dimens.xml 长什么样:

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <dimen name="dp_1">1.1111dp</dimen>
    <dimen name="dp_2">2.2222dp</dimen>
    <dimen name="dp_3">3.3333dp</dimen>
    <dimen name="dp_4">4.4444dp</dimen>
    <dimen name="dp_5">5.5556dp</dimen>
    <dimen name="dp_6">6.6667dp</dimen>
    <dimen name="dp_7">7.7778dp</dimen>
    <dimen name="dp_8">8.8889dp</dimen>
    <dimen name="dp_9">10.0000dp</dimen>
    <dimen name="dp_10">11.1111dp</dimen>
    ...
    <dimen name="dp_355">394.4444dp</dimen>
    <dimen name="dp_356">395.5556dp</dimen>
    <dimen name="dp_357">396.6667dp</dimen>
    <dimen name="dp_358">397.7778dp</dimen>
    <dimen name="dp_359">398.8889dp</dimen>
    <dimen name="dp_360">400.0000dp</dimen>
</resources>

values-sw400dp 指的是当前设备屏幕的 最小宽度 为 400dp,把屏幕宽度同样分为 360份,这时每份就等于 1.1111dp 了,每个引用都递增 1.1111dp,值最大的 dimens 引用 dp_360 同样刚好覆盖屏幕宽度,为 400dp。

通过两个 dimens.xml 文件的比较,dimens.xml 的生成原理一目了然,方案会先确定 最小宽度基准值,然后将每个 values-swdp 中的 dimens.xml 文件都分配与 最小宽度基准值 相同的份数,再根据公式 屏幕最小宽度 / 份数 (最小宽度基准值) 求出每份占多少 dp,保证不管在哪个 values-swdp 中,份数 (最小宽度基准值) * 每份占的 dp 值 的结果都是刚好覆盖屏幕宽度,所以在 份数 不变的情况下,只需要根据屏幕的宽度在不同的设备上动态调整 每份占的 dp 值,就能完成适配。

这样就能保证不管将项目运行到哪个设备上,只要当前设备能匹配到对应的 values-swdp 文件夹,那布局中的 dimens 引用就能根据当前屏幕的情况进行缩放,保证能完美适配。
如果没有匹配到对应的 values-swdp 文件夹,也没关系,它会去寻找与之相近的 values-swdp 文件夹,虽然在这种情况下,布局中的 dimens 引用的值可能有些许误差,但是也能保证最大程度的完成适配。

说到这里,那大家就应该就会明白我为什么会说 smallestWidth 限定符屏幕适配方案 的原理也同样是按百分比进行布局,如果在布局中,一个 View 的宽度引用 dp_100,那不管运行到哪个设备上,这个 View 的宽度都是当前设备屏幕总宽度的 360分之100,前提是项目提供有当前设备屏幕对应的 values-swdp,如果没有对应的 values-swdp,就会去寻找相近的 values-swdp,这时就会存在误差了,至于误差是大是小,这就要看您的第二个因数怎么分配了。

因素二
第二个因数是需要适配哪些 最小宽度?比如您想适配的 最小宽度 有 320dp、360dp、400dp、411dp、480dp,那方案就会为您的项目生成 values-sw320dp、values-sw360dp、values-sw400dp、values-sw411dp、values-sw480dp 这几个资源文件夹,像这样:

├── src/main
│   ├── res
│   ├── ├──values
│   ├── ├──values-sw320dp
│   ├── ├──values-sw360dp
│   ├── ├──values-sw400dp
│   ├── ├──values-sw411dp
│   ├── ├──values-sw480dp

方案会为您需要适配的 最小宽度,在项目中生成一系列对应的 values-swdp,在前面也说了,如果某个设备没有为它提供对应的 values-swdp,那它就会去寻找相近的 values-swdp,但如果这个相近的 values-swdp 与期望的 values-swdp 差距太大,那适配效果也就会大打折扣
那是不是 values-swdp 文件夹生成的越多,覆盖越多市面上的设备,就越好呢?
也不是,因为每个 values-swdp 文件夹其实都会占用一定的 App 体积,values-swdp 文件夹越多,App 的体积也就会越大
所以一定要合理分配 values-swdp,以越少的 values-swdp 文件夹,覆盖越多的机型。

总结
在使用 smallestWidth 最小宽高限定符屏幕适配方案 时,需要提供 最小宽度基准值 和需要适配哪些 最小宽度。
假设设计图总宽度为 375 dp,一个 View 在这个设计图上的尺寸是 50dp * 50dp,这个 View 的宽度占整个设计图宽度的 13.3% (50 / 375 = 0.133)
我们就把 最小宽度基准值 设置为 375 (和 设计图 一致),这时方案就会为我们需要适配的 最小宽度 生成对应的 values-swdp 文件夹,文件夹中的 dimens.xml 文件是由从 1 到 375 组成的 dimens 引用,把所有设备的屏幕宽度都分为 375 份,所以在布局文件中我们应该把这个 View 的高宽都引用 dp_50。
在适配时:
设备 1 的屏幕总宽度为 1080 px,屏幕总高度为 1920 px,DPI 为 480
设备 1 的屏幕高度大于屏幕宽度,所以 设备 1 的 最小宽度 为屏幕宽度,再根据公式 px / (DPI / 160) = dp,求出 设备 1 的 最小宽度 的值为 360 dp (1080 / (480 / 160) = 360),所以根据 设备 1 的 最小宽度 应该匹配的是 values-sw360dp 这个文件夹,也就是说如果将屏幕宽度分为360份那么每份的值为1dp,但是现在我们最小宽度基准值为375,360 / 375 = 0.96,所以每份占的 dp 值为 0.96,那么生成的dimens.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <dimen name="dp_1">0.96dp</dimen>
    <dimen name="dp_2">1.92dp</dimen>
    <dimen name="dp_3">2.88dp</dimen>
    <dimen name="dp_4">3.84dp</dimen>
    <dimen name="dp_5">4.8dp</dimen>
    ...
    <dimen name="dp_50">48dp</dimen>
    ...
    <dimen name="dp_371">356.16dp</dimen>
    <dimen name="dp_372">357.12dp</dimen>
    <dimen name="dp_373">358.08dp</dimen>
    <dimen name="dp_374">359.04dp</dimen>
    <dimen name="dp_375">360dp</dimen>
</resources>

使用中的问题:
这时有人就会问了,设计师给的设计图只标注了 px,使用这个方案时,那不是还要先将 px 换算成 dp?
其实也可以不用换算的,你只需要把设计图的 px 总宽度设置成 最小宽度基准值 就可以了。

2,今日头条适配方案(更新)

未完,待续

猜你喜欢

转载自blog.csdn.net/gpf1320253667/article/details/84872376