Android屏幕适配方案

·  阅读 2366

目前比较流行的屏幕适配方案是今日头条适配方案和smallest width限定符适配方案 首先来说一下今日头条的屏幕适配方案:

简介:我们写UI界面的时候,都是使用dp作为单位,UI给出的设计图也是以dp为单位,比如UI给的图是宽x高为360x640dp的。那么我们为了达到在所有手机上都能不变形的展示UI图,那么我们所有的手机都要是宽x高 = 360 x 640dp,很显然,实际情况不是这样的,因为dp = px / density; px就是手机的像素,不同手机的像素不一样,不如有1080 x 1920 和 720 X 1280 像素的手机。density = dpi / 160; dpi是这样生成的

那么我们就可以知道,不同手机宽度和高度上的dp值是不相同的。

那么我们目前要做的事情就是根据手机实际的像素,算出density,使宽度上的 dp = px / density = 360 高度上的dp = px / density = 640; 因为每个手机像素都是固定的,所以就可以改变density的值就可以了。

因为 density = dpi / 160; 所以改变density就是改变dpi就可以了。

在说明一下,因为每个手机在宽和高上的像素比例不一样,所以无法做到在宽度和高度上完全的适配,如果我们的这个界面能在上下方向上滑动,就在宽度上适配就好了,如果我们的界面在高度上无法滑动,宽度上就尽量使用match_parent, wrap_content, 权重,就好了,意思就是没有完美无缺的适配方式。

那么我们下面的重点就是求出dpi,因为dpi = 160 * (px / dp); 就像我们上面所说的:手机的像素是确定的,即px是确定的,dp就是我们要适配成的dp值,也是确定的,比如宽度上是360,那么我们就可以算出,dpi值,算出之后就可以动态改变这个值,使我们所有的适配的手机的宽度上的dp值是360,或高度上是640。

今日头条的适配方案原理就是这样的,下面我们看看实现方法:

通过阅读源码,我们得知,density是DisplayMetrics中的成员变量,而DisplayMetrics实例通过Resource#getDisplayMetrics可以获得,而Resource通过Activity或者Application的Context获得

先来熟悉一下DisplayMetrics中和适配相关的几个变量: DisplayMetrics#density就是上述的density DisplayMetrics#densityDpi就是上述的dpi DisplayMetrics#scaledDensity字体的缩放因子,正常情况下和density相等,但是调节系统字体大小后会改变这个值。

那么是不是所有的px和dp的转换都是通过DisplayMetrics中相关的值来计算的呢? 首先来看看布局文件中dp的转换,最终都是调用TypedValue#applyDimension(int unit,float value,DispalyMetrics metrics);来进行转换的:

这里用到的DisplayMetrics正是从Resource中获取的。

再看看图片的decode,BitmapFactory#decodeResourceStream方法:

可见也是使用了DisplayMetrics中的dpi值。

基本上所有的dp转px的场景都需要使用DisplayMetrics来计算。

最终解决方案:

下面假设设计图宽度是360dp,以宽度来适配: 那么适配后的density = 设备的真实宽(单位px)/360,接下来只需要我们把计算好的density在系统中修改下即可,实现代码如下:

同时在Activity#onCreate方法中调用下,代码比较简单,侵入性比较低,

对字体大小sp的处理方式为

在系统中切换字体大小,还需要使用Application#resisterComponentCallbacks注册监听onConfigurationChanged监听: 最终的方案为

下面再来看看smallest width限定符适配方案:

再来复习一下什么是dp?

dp指的是设备独立像素,以dp为单位尺寸的控件,在不同分辨率和尺寸的手机上代表了不同的真实的分辨率,比如在低分辨率的手机上,1dp = 1px,在高分辨率的手机上,可能1dp = 2px。,这样的话,一个96*96dp的控件,在不同分辨率的手机上表现的大小就差不多了。

什么是dpi?

dpi是像素密度,指的是在系统软件上指定的单位尺寸上的像素数量。它往往是写在系统出厂配置文件中的一个固定值。

为什么我们要强调它是软件系统上的概念?

因为大家买手机的时候,往往会听到另一个叫ppi的参数,这个在手机屏幕中指的也是像素密度,但是这个是物理上的概念,它是客观存在的不会改变的。dpi是软件参考了物理像素密度后,认为指定的一个值,这样保证了某一个区间内的物理像素密度在软件上都使用同一个值,这样会有利于我们的UI适配。

比如,几个相同分辨率不同尺寸的手机上的ppi可能分别是430,440,450,那么在Android系统中,可能dpi会全部指定为480,这样的话,dpi/160就会是一个相对固定的值,这样就能保证相同分辨率不同尺寸的手机表现得一致。

而在不同分辨率下,dpi将会不同,比如:

根据上面的表格,我们可以发现,720p和1080p的手机,dpi是不同的,这也就意味着,不同的分辨率中,1dp对应着不同数量的px(720p中,1dp = 1px,1080p中,1dp = 3px),这样就实现了,当我们使用dp来定义一个控件大小的时候,它在不同的手机上表现出相应大小的像素值。

我么可以说,通过dp加上自定义布局和weight比例布局,可以基本上解决不同手机上适配的问题,这是最基本最原始的适配方案。

这种适配方式,存在两个小问题,第一:这样只能保证我们写出来的界面适配大部分手机,部分手机仍然需要单独适配,因为可能在相同分辨率的手机上dpi可能不同。 第二个问题就是设计图可能根据某个像素的手机设计的,那么在其他手机上就会有很大偏差。

宽高限定符适配

为了高效实现新的适配方案,我们把它称作宽高限定符适配,简单的说,就是穷举市面上所有的Android手机的宽高像素值:

设定一个基准的分辨率,其他分辨率都根据这个基准分辨率来计算,在不同的尺寸文件内部,根据该尺寸编写对应的dimens文件。

比如以480x320为基准分辨率

宽度为320,将任何分辨率的宽度分为320份,取值为x1-x320      
高度为480,将任何分辨率的高度整分为y1-y480
复制代码

那么对于800*480的分辨率的dimens文件来说:

x1 = (480/320)*1 = 1.5px;
x2 = (480/320)*2 = 3px;
复制代码

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

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

不过这个方案有一些团队用过,我们可以认为它是一个比较成熟有效的方案了。

smallest width 限定符适配:

smallestWidth适配,或者叫sw限定符适配。指的是Android会识别屏幕可用高度和宽度的最小尺寸的dp值(其实就是手机的宽度值),然后根据识别到的结果去资源文件中寻找对应限定符的文件夹下的资源文件。

这种机制和上文提到的宽高限定符适配原理上是一样的,都是系统通过特定的规则来选择对应的文件。

举个例子,小米5的dpi是480,横向像素是1080px,根据px=dp(dpi/160),横向的dp值是1080/(480/160),也就是360dp,系统就会去寻找是否存在value-sw360dp的文件夹以及对应的资源文件。

smallestWidth限定符适配和宽高限定符适配最大的区别在于,前者有很好的容错机制,如果没有value-sw360dp文件夹,系统会向下寻找,比如离360dp最近的只有value-sw350dp,那么Android就会选择value-sw350dp文件夹下面的资源文件。这个特性就完美的解决了上文提到的宽高限定符的容错问题。

这套方案是上述几种方案中最接近完美的方案。

首先,从开发效率上,它不逊色于上述任意一种方案。根据固定的放缩比例,我们基本可以按照UI设计的尺寸不假思索的填写对应的dimens引用。

我们还有以375个像素宽度的设计稿为例,在values-sw360dp文件夹下的dimens文件应该怎么编写呢?

这个文件夹下,意味着手机的最小宽度的dp值是360,我们把360dp等分成375等份,每一个设计稿中的像素,大概代表smallestWidth值为360dp的手机中的0.96dp,那么接下来的事情就很简单了,假如设计稿上出现了一个10px*10px的ImageView,那么,我们就可以不假思索的在layout文件中写下对应的尺寸。

而这种diemns引用,在不同的values-swdp文件夹下的数值是不同的,比如values-sw360dp和values-sw400dp,

当系统识别到手机的smallestWidth值时,就会自动去寻找和目标数据最近的资源文件的尺寸。

其次,从稳定性上,它也优于上述方案。原生的dp适配可能会碰到Pixel 2这种有些特别的手机需要单独适配,但是在smallestWidth适配中,通过计算Pixel 2手机的的smallestWidth的值是411,我们只需要生成一个values-sw411dp(或者取整生成values-sw410dp也没问题)就能解决问题。

smallestWidth的适配机制由系统保证,我们只需要针对这套规则生成对应的资源文件即可,不会出现什么难以解决的问题,也根本不会影响我们的业务逻辑代码,而且只要我们生成的资源文件分布合理,,即使对应的smallestWidth值没有找到完全对应的资源文件,它也能向下兼容,寻找最接近的资源文件。

当然,smallestWidth适配方案有一个小问题,那就是它是在Android 3.2 以后引入的,Google的本意是用它来适配平板的布局文件(但是实际上显然用于diemns适配的效果更好),不过目前所有的项目应该最低支持版本应该都是4.0了(糗事百科这么老的项目最低都是4.0哦),所以,这问题其实也不重要了。

还有一个缺陷我忘了提,那就是多个dimens文件可能导致apk变大,这是事实,根据生成的dimens文件的覆盖范围和尺寸范围,apk可能会增大300kb-800kb左右,目前糗百的dimens文件大小是406kb,我认为这是可以接受的。

参考: mp.weixin.qq.com/s/d9QCoBP6k…
mp.weixin.qq.com/s/X-aL2vb4u…

分类:
Android
标签:
分类:
Android
标签: