Android适配——drawable和values的加载规则

Google搞了一大套 dip、sp、mdpi、hdpi、xhdpi之类的这些东西,简单说来,就是为了让我们轻松实现“与设备密度无关的视觉大小一致性”,这里需要明确的“视觉大小一致性”,就是说无论是在手机、低分辨率平板、高分辨率平板上,这个控件或者图片在物理尺寸上都是一样的。

这里我们借 @雨打萍 的一张图片来看看:
(出处: http://blog.csdn.net/xiebudong/article/details/37040263

其中,黑色和蓝色矩形的视觉大小就是一致的。

另外一个需要明确的是屏幕密度、分辨率、物理尺寸之间的关系:
这里写图片描述

以一个分辨率为1920×1200,物理尺寸为7寸的手持平板而言,根据勾股定理,我们可以得出其对角线上的像素数大约为2264,用2264除以7就是此屏幕的屏幕密度,结果为323.
也就是说,屏幕密度、分辨率、物理尺寸可以由二推一,hdpi-1920×1200屏幕的物理尺寸绝对要比hdpi-1280×800大,这也是我们之后讨论适配的前提。

· 实际密度与系统密度
我们经常见到的Android设备屏幕密度大多为120、160、240、320、480等,而上述例子中的323dpi则是设备的实际密度,说明这块屏幕每寸有323个像素。得到实际密度以后,Android系统推荐选择一个最近的密度作为系统密度,通过DisplayMetrics类获取上述设备的系统密度就是320dpi。
但是,现在很多Android厂商不一定会选择这些值作为系统密度,而是选择实际的dpi作为系统密度,这就导致了很多手机的dpi也不是在这些值内。例如小米Note这样的xxhdpi的设备他的系统密度并不是480,而是它的实际密度440。

那么针对这些非规范的系统密度,Google官方指定按照系列标准进行区分:
这里写图片描述

有了这两个前提,再来看android的适配规则就很清楚了:


drawable目录

我们经常会给应用程序切几套图片,放在drawable-mdpi、drawable-hdpi、drawable-xhdpi等目录下面。当应用在设备对应dpi目录下没有找到某个资源时,遵循“先高再低”原则,然后按比例缩放图片:

  • 比如,当前为xhdpi设备,并且只有以下几个目录,则drawable的寻找顺序为:
    xhdpi->xxhdpi->xxxhdpi(如果没有更高的了)->nodpi(如果有的话)->hdpi->mdpi,如果在xxhdpi中找到目标图片,则压缩2/3来使用,如果在mdpi中找到图片,则放大2倍来使用。

这很好理解,如果我们按规则放置两张图片,mdpi中为48×48,xxhdpi中为144×144,那么不管我们最后从哪个目录拿到图片,在xhdpi设备上显示的像素大小都是96×96,只是一个被拉伸而来,一个被压缩而来。由于xhdpi定义了96个像素点的物理尺寸,那么这张图的物理尺寸实际就被定下来了。
同样的,mdpi中48个像素点的物理尺寸与xhdpi中96个像素点的物理尺寸是相同的,这就保证了该图片在任何设备上显示出的视觉大小一致。
那么,一个结论就是,对于期望保持视觉大小一致的那部分图片而言,如果你也能接受android为你拉伸/压缩图片导致一定程度的模糊或者锐化,那么这些图片是不需要在每个drawable目录下都制作一份的。以现在主流设备来说一般可能在drawable-xxhdpi放置一份即可,这样可以尽量避免Android为我们放大图片所导致的OOM。

当然,在某些情况下,我们会主观希望打破android提供的“视觉大小一致”这种机制,此时我们就可以建立另外的drawable目录来放置需要变化的图片了。

然后附一份比例表吧:
这里写图片描述


values目录

values目录用来放置colors.xml,dimens.xml,strings.xml等,也可以根据屏幕密度设置特定的values目录让满足设定的设备进行加载,比如values-mdpi、values-hdpi、values-xhdpi、values-xxhdpi等等,然后每个目录放置一个demins.xml,使不同分辨率的设备应用不同的尺寸设置。当应用设备在当前dpi对应目录的demins.xml中没有找到目标条目时,采用“就近匹配”原则:

  • 比如,当前为hdpi设备,并且只有以下几个目录,则values的寻找顺序为:
    hdpi->xhdpi->mdpi->values,即先向上级dpi目录查找,再向下级dpi目录查找,最后一路向下查找到values目录,如果values下都找不到,就只有找values-ldpi,当然,现在有这个目录的应用不多了。

那么,我们需要将mdpi目录下的值都乘以相应的倍数来放置在其他目录下面吗?答案当然是否定的,由于我们对期望屏幕密度无关的值都定义为了dp或者dip的单位,无论android从哪个目录最终找到该值,都会直接应用这个值与当前设备的密度来计算最终的尺寸。

也就是说,如果我们同样在values-xhdpi和values下写 length=10dp
那么在mdpi设备上得到的都是10px,在xhdpi设备上得到的都是20px。
但它们看起来“都是一样宽”,这样就已经是“保证视觉大小一致性了”。
48dp法则告诉我们,48dp的物理尺寸约等于9mm,是人的手指比较容易点击到的大小,并且是独立于设备的。

显而易见,如果我们在values目录下写length=10dp,values-xhdpi目录下写length=20dp,mdpi设备上得到的将是10px,而xhdpi设备上得到的将是40px,得到视觉结果就是,该控件在xhdpi设备上看起来比mdpi设备上大了一倍。

  • 考虑这样一个情况:有一个BottomBar,左右两端各有一个按钮,按钮长宽用dp定义,这样在一个大屏手机中,两个按钮可能就相隔更远了,因为按钮的视觉尺寸是没有变化的。如果你想保持按钮在BottomBar中所占的比例,最好办法不是添加一个values-xxx,然后重写这两个dp值,而是精心设计你的布局。

那么,既然最后都要找到values,并且能够保证视觉大小一致性,那何必再添加其他values分辨率目录呢?答案是在某些情况下,我们主观希望某些尺寸不去保持视觉一致性。例如一个Button,在手机上那么大刚好,但如果在平板设备上,是的,它看起来和在手机上一样大,但是,它显得有点小了。

也就是说,我们应该把希望在任何设备上视觉大小都一样的尺寸都放置在values目录下并且只放置这一份,其他需要有变化的尺寸则放置在对应目录下即可

一般而言,使用在物理尺寸相差不大的几套设备上,一个values可能就够了,因为它本身就保证了“视觉大小一致性”,但是如果你的应用需要兼容平板,甚至电视,那么这种一致性可能是一种灾难。这时可以考虑添加一个对应dpi的values目录,把需要变化的值拷贝进去重写,但我更推荐采用values-xhdpi-2560×1600,我们很容易通过这里的屏幕分辨率+dpi计算得到该设备的物理尺寸,显而易见这是一个平板设备,如此我们的改动便不至于影响同DPI的低物理尺寸设备(手机),而物理尺寸差不多的设备是可以共用一套dimens.xml的。

  • 那么,如果当前设备为xhdpi-1184×800,当前目录有values-xhdpi-1184×800,values-xhdpi-1184×960,values-xhdpi-1184×720,android的寻找顺序则是:
    values-xhdpi-1184×800->values-xhdpi-1184×720->values-xhdpi

只向低于自己分辨率的目录下寻找,直到values-xhdpi,如果依然没有找到,按照之前的顺序继续进行。(hdpi-1280×800 -> hdpi-1280×720 -> hdpi -> …)
也就是说,对于同dpi的多台不同分辨率平板设备,如果布局足够通用,我们可以只针对最低分辨率设计dimens即可,上面的例子中,则是values-xhdpi-1184×720。
我们还可以将这个分辨率写得更低,低到我们有把握:如果再出现比这个分辨率更低的设备,那么它的物理尺寸一定满足即使采用values-xhdpi中针对手机物理尺寸设计的大小也没有问题。

0 条评论
发表一条评论