Android开发必备知识--适配问题(全)+资源值加载问题+dpi分辨率问题

一些名词的解释:

      (1)屏幕尺寸:单位英寸(inch),指的是屏幕对角线长度。

  (2)屏幕密度:单位dpi,指的是每inch上可以显示多少像素点即px。

  (3)屏幕分辨率:单位px * px,如1280×800,我使用的小米853×480等,指的是一屏显示多少像素点。

  (4)屏幕无关像素:单位dp/dip,指的是自适应屏幕密度的像素,用于指定控件宽高。

  (5)刻度无关像素:单位sp,指的是自适应字体的像素,用于指定文字大小。

      (6)更精确的讲法:ppi

关于安卓适配问题是比较麻烦的,其中理解drawable资源文件加载方式和一些必要的分辨率问题是非常重要的。

in:英寸inch,物理尺寸,1in =2.54厘米(cm)。4.2寸手机等等就是这个in,通常说的手机4.2寸,都是手机对角线4.2in。

dpi:这个知道英文名有助于理解意义,its english name is:  Dots Per Inch.每英寸的点数嘛,每英寸的像素个数。例如:320X480分辨率的手机,宽2in,高3in,那么每英寸像素点个数(dpi)是:320/2=160.使用正方形像素点横纵向计算结果一样,一般是计算对角线。

density:这个搅屎棍极具混淆作用,这货是屏幕密度,屏幕密度density和dpi的关系是density = dpi/160,协议规范。这个是对dpi的一个规范,160dpi就是密度为1的意思。

dp:主角闪亮登场,也就是dip,设备独立像素,device independent pixels的简写,android特有,在屏幕密度为1也就是dpi=160的屏幕,1dp = 1px。这是规范。

sp:和dp类似,用来设置字体,和dp不同的是它可以自动根据用户的字体大小偏好来缩放,比如说你字体用的sp,用户系统设置字体偏好是偏大,那么sp会相应放大字体,而如果你用dp,则不会放大。

px:pixel,像素,屏幕上的点,是数码设备最小的独立显示单位,px均是整数,分辨率480X800就是,像素点个数。

说到这里,问题来了,一个图片加载到内存的话占用多大空间呢?一个32位的图分辨率是1280*768,所占内存大小:(1280*768*(32/8))/(1024*1024) = 3.75MB    像素点数*一个像素点所占用的byte数,32位图表示一个像素占用32个bit位,也就是4byte。

ppi: 和屏幕密度一个意思, 全称是pixel per inch.  是专业一点的叫法.

一个bitmap,是分辨率是1024*1024,要是32位的大小就是32/8=4m。

适配的时候drawable级别:android会根据屏幕尺寸自动选择相应资源文件进行渲染,sdk检测到你的手机dpi是160的话,优先去drawable-mdpi下找相应图片资源,找不到会区别的文件夹找,并根据density做相应缩放。比如dpi。=160的设备,wrap-content设置资源,在mdpi没找到图片,从xhdpi找到了,240X240px的图片,240会除以2乘以1得到120px

关于Drawable资源对应dpi值
mdpi 120dpi-160dpi
hdpi 160dpi-240dpi
xhdpi 240dpi-320dpi
xxhdpi 320dpi-480dpi
xxxdpi 480dpi-640dpi

当一个apk运行起来时,Android系统会根据其所运行的手机的屏幕密度去相对应的图片文件夹里找指定名称的图片。 注意, 先去哪个目录里找,完全是根据这个手机的屏幕密度决定的。


其中注意两点:

1, 中等分辨率,即mdpi的屏幕密度是160,他是标准的参考密度。所以计算比例的时候它的比例值是1. 其他屏幕密度的参考比例都是以这个为依据。

2, 默认的drawble目录(一般是自己建的),和mdpi是一样的。将图片放到这个目录和放到drawble-mdpi目录是一样的效果。不过一般习惯性的放一些自定义selector或者点9的图片在这里。

现在我们来看, HTC one V手机的屏幕密度是252ppi, 那距离哪一个最靠近呢, 就是hdpi了。 所以当apk运行在这个手机上时,首先会去这个目录找图片。

下面是用常见的一些类型的手机总结的一个表格:

需要明确的是屏幕密度、分辨率、物理尺寸之间的关系: (参考:https://blog.csdn.net/cloud_castle/article/details/52313858)

这里写图片描述

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

· 实际密度与系统密度 
我们经常见到的Android设备屏幕密度大多为120、160、240、320、480等,而上述例子中的323dpi则是设备的实际密度,说明这块屏幕每寸有323个像素。得到实际密度以后,Android系统推荐选择一个最近的密度作为系统密度,通过DisplayMetrics类获取上述设备的系统密度就是320dpi。 

但是,现在很多Android厂商不一定会选择这些值作为系统密度,而是选择实际的dpi作为系统密度,这就导致了很多手机的dpi也不是在这些值内。例如小米Note这样的xxhdpi的设备他的系统密度并不是480,而是它的实际密度440。

drawable目录

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

  • 比如,当前为xhdpi设备,并且只有以下几个目录,则drawable的寻找顺序为: 
    xhdpi->xxhdpi->xxxhdpi(如果没有更高的了)->nodpi(如果有的话)->hdpi->mdpi,如果在xxhdpi中找到目标图片,则压缩2/3来使用,如果在mdpi中找到图片,则放大2倍来使用。
  • 上面说的对应关系,都是首选目录, 那如果首选目录里面找不到图片呢? 

    Android图片选择策略

    如果屏幕所对应的文件夹没有要找的图片,怎么办。这是很常见的,我们开发项目时一般不会去为每一个级别的屏幕去切一套图片。那样做只会让apk很大。所以一般性的图片我们只切一两个典型密度屏幕的图片。但是apk是有可能会运行在从ldpi到xxhdpi的各种级别的手机上。这个时候就需要根据一定的策略去寻找图片了。步骤是这样的:

    1, 去屏幕密度对应的目录去找。如果找到就拿来用。

    2, 如果没找到,就去比这个密度高一级的目录里面去找,如果找到就拿来用。

    3, 如果没找到就继续往上找。以此类推。

    4, 如果到了xxhdpi目录还没有找到的话,就会去比自身屏幕密度低一级的目录去找,如果低一级的目录>=hdpi,找到了就拿来用。

    5, 如果没找到, 就去mdpi目录去找, 如果找到了,就拿来用。

    6, 如果没找到,就去默认的drawble目录里去找, 如果找到了就拿来用。

    7 ,如果没找到,再去最低的ldpi目录里去找。如果找到了,就拿来用。

    8, 如果没找到, 那就是没找到了, 图片无法显示。(不过一般不会出现这种现象,因为如果每个目录都没有这个图片的话,你是编译不过的)

    这里有两点需要注意:

    ①  首先会去比自己密度高的目录里去找,这是因为因为系统相信,你在密度更高的目录里会放置分辨率更大的图片,这样的话这个图片会被缩小,但同时显示效果不会有损失,但是如果优先去低一级别的目录去找的话, 找到的图片就会被放大,这样的话这个图片就会被拉扯模糊了。

    e.g. 同一张图片,你在mdpi和xxhdpi目录各放了一份, 这个应用你现在运行在hdpi的手机上, 那应用会选择哪张图片呢。答案是xxhdpi目录里的。即便hdpi离mdpi更近一点!

    ②,如果在mdpi里找不到是不会直接去ldpi里找的, 而是先去默认的drawble目录里找,这是drawble目录和drawble-mdpi是一个级别的。

    下面用一张流程图来总结:

     


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

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

这里写图片描述

values目录(参考https://blog.csdn.net/cloud_castle/article/details/52313858)

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-2560x1600,我们很容易通过这里的屏幕分辨率+dpi计算得到该设备的物理尺寸,显而易见这是一个平板设备,如此我们的改动便不至于影响同DPI的低物理尺寸设备(手机),而物理尺寸差不多的设备是可以共用一套dimens.xml的。

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

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


关于字体大小的适配

思路是如上图,为不同分辨率,不同密度准备一套字体大小的尺寸

在values-xxx-xxx文件夹下创建dimen。xml文件:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="size_title">20sp</dimen>
    <dimen name="size_name">18sp</dimen>
    <dimen name="size_code">10sp</dimen>
</resources>

有一些我在配置过程中碰到的问题和大家分享一下:

1.文件夹命名:

    如上图,有些错误的文件夹,我的结论是分辨率x号前面的数值要比后面的大,不然系统无法识别,

注意:乘号的输入法,千万别用汉字输入法下的x,不然系统无法系别,不要问我为什么,我是不会告诉你这玩意花了我起码3个小时才搞明白

2.系统会自动匹配values文件夹

  举个例子,之前提到的(2)1280x800的,如果你创建values-mdpi-1250x800,values-mdpi-1200x800,(注意了这两个都不是1280x800的),最后真机联调的时候,显示的是比真实分辨率小的最大的那套尺寸即values-mdpi-1250x800,而不是1200x800,这点上虚拟机和真机有很大的区别,xml视图只会找1280x800的尺寸,如果没有,他就显示默认大小的字体。

可以参考下这一篇:http://www.cnblogs.com/zealotrouge/archive/2012/11/23/2784774.html

3。如果系统系统自动匹配的时候没有找到最小的尺寸,就会报错

 所以建议在values下dimen.xml下保存一份最小的字体尺寸,再难看也好过报错,是吧


此博客仅用作交流学习与自我参考,参考博客:https://blog.csdn.net/xiebudong/article/details/37040263

发布了149 篇原创文章 · 获赞 132 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/Sailor_luo/article/details/80306080