admin 管理员组

文章数量: 887007

android ppi 对应资源,Android适配原则及资源加载规则

一. 名词解释

o 像素(Pixel, PX)

表示屏幕上一个物理的像素点。

o 屏幕尺寸

屏幕的物理尺寸,一般用屏幕对角线长度来表示,单位英寸(inch)。1in

=2.54cm。通常我们说手机5寸屏,指的就是手机屏幕的对角线长度是5寸。

o 屏幕分辨率(Screen Resolution)

屏幕垂直方向像素数目px *

屏幕水平方向像素数目px,如1280×800,表示屏幕横向有800个像素点,纵向有1280个像素点。相同尺寸屏幕下屏幕分辨率越高显示效果越精细细腻。

o 像素密度(PPI, Pixels Per Inch)

表示每inch上可以显示的像素点数目,单位px。 与屏幕尺寸、分辨率的关系如下:

o 屏幕密度(DPI,Dots Per Inch)

表示每Inch上可以显示的(打印)点数,单位dpi。如160dpi。在打印机设备上常用。PPI和DPI的详细区别请看/。Android提供了DisplayMetrics类和Configuration类获取当前屏幕密度:

context.getResources().getDisplayMetrics().densityDpi

context.getResources().getConfiguration().smallestScreenWidthDp

o 密度无关像素(DP/DIP,Density Independent Pixels)

Android特有的长度单位,可以理解为与具体设备无关的虚拟像素。在特定设备上显示时系统会自动根据屏幕密度确定最终对应的物理长度(px单位)。所以用dp定义的UI可以自适应各种屏幕。Android规定在屏幕密度160dpi的设备上1dp

= 1px。所以虚拟像素dp和物理像素px的换算关系是:

具体函数实现如下,其中density=DPI/160。

public static intdp2px(Context context, floatdpValue){

floatscale=context.getResources().getDisplayMetrics().density;return(int)(dpValue * scale + 0.5f);}

public static intpx2dp(Context context, floatpxValue){

floatscale=context.getResources().getDisplayMetrics().density;return(int)(pxValue / scale + 0.5f);}

o 比例无关像素(SP, Scale Independent Pixels)

Android特有的字体大小单位,可以理解为与具体字体缩放比例无关的虚拟字体大小。在特定设备上显示时系统会自动根据字体缩放比例确定最终字体大小。所以用sp定义的字体大小可以自适应各种大小的系统字体设置。Android规定在屏幕密度160dpi的设备上缩放比例为100%时1sp

= 1px, 所以sp和物理像素px的换算关系是:

具体函数实现如下,其中scaledDensity=scale * (DPI/160)。

public static intsp2px(Context context, floatspValue){

floatfontScale = context.getResources().getDisplayMetrics().scaledDensity;return(int) (spValue * fontScale + 0.5f);}

public static intpx2sp(Context context,floatpxValue){

floatfontScale = context.getResources().getDisplayMetrics().scaledDensity;return(int) (pxValue / fontScale + 0.5f);}

所以使用sp定义的字体是根据系统字体设置而变化的。如果不希望UI的字体大小随系统设置变化,可以用dp定义字体大小。

二. 视觉大小一致性

Android为了使一套UI自动适应不同分辨率的屏幕,实现“与设备密度无关的视觉大小一致性”,也就是说无论是在高分辨率手机、还是在低分辨率的平板上,某个控件或者图片在物理尺寸上都是一样的,这样UI就不会因为设备的不同而显示得千奇百怪,才不会让用户觉得别扭。

所以发明了dp并定义了dp与物理像素的对应关系,同时把手机屏幕根据屏幕密度分成5个级别:

根据上表,为了让APP的UI或者说图片在不同屏幕密度上保持视觉大小一致,也就是在不同屏幕上占有相同的dp,

那么应该针对不同屏幕提供特定比例的布局或图片, 比如mdpi下某个icon尺寸是48X48,

那么这个icon在ldpi下应该为36X36, hdpi为72X72, xhdpi为96X96,

xxhpdi为144X144,xxxhdpi为192X192。

三.

Android适配规则

o UI元素的大小尽量以dp为单位或直接指定为 wrap_content。

o LinearLayout中尽量使用weight分配大小。

o

图片由于缩放可能会导致变形,在小屏幕上加载大图片会占用过多内存,因此需要针对不同屏幕密度的设备提供不同大小的图片。一般会提供drawable-mdpi、drawable-hdpi、drawable-xhdpi等图片目录以适应不同设备。

o

dimens适配,可以根据屏幕密度设置特定的values目录让满足设定的设备进行加载,比如values-mdpi、values-hdpi、values-xhdpi、values-xxhdpi、values-800x400等等,然后每个目录放置一个demins.xml,使不同分辨率的设备应用不同的尺寸设置。

o 字符串国际化,可以建立values-zh、values-en等多语言目录。

o 图片国际化,

可以建立drawable-en-mdpi、drawable-en-hdpi等语言相关的drawable目录。

o 如果需要为不同屏幕单独写不同的layout,

那么layout目录也可以根据屏幕密度设置多个目录。如layout-1280x720、layout-800x480 。

o 横竖屏切换时使用不同layout,那么就设计layout-port和layout-land两个目录。

四.

Android资源加载规则

o 图片加载

当App在特定设备上运行时对应dpi目录下没有找到某个资源时,遵循“先高再低”原则,然后按比例缩放图片。比如,当前为xhdpi设备,并且只有以下几个目录,则drawable的寻找顺序为:

xhdpi->xxhdpi->xxxhdpi(如果没有更高的了)->nodpi(如果有的话)->hdpi->mdpi,如果在xxhdpi中找到目标图片,则压缩2/3来使用,如果在mdpi中找到图片,则放大2倍来使用。

这很好理解,如果我们按规则放置两张图片,mdpi中为48x48,xxhdpi中为144x144,那么不管我们最后从哪个目录拿到图片,在xhdpi设备上显示的像素大小都是96x96,只是一个被拉伸而来,一个被压缩而来。由于xhdpi定义了96个像素点的物理尺寸,那么这张图的物理尺寸实际就被定下来了。同样的,mdpi中48个像素点的物理尺寸与xhdpi中96个像素点的物理尺寸是相同的,这就保证了该图片在任何设备上显示出的视觉大小一致。

那么,一个结论就是,对于期望保持视觉大小一致的那部分图片而言,如果你也能接受android为你拉伸/压缩图片导致一定程度的模糊或者锐化,那么这些图片是不需要在每个drawable目录下都制作一份的。以现在主流设备来说一般可能在drawable-xxhdpi放置一份即可,这样可以尽量避免Android为我们放大图片所导致的OOM。

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

o values加载

当App在特定设备上运行,如果在当前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中针对手机物理尺寸设计的大小也没有问题。

再举一个复杂的例子

=============

在 720x1280 xhdpi 的机器上,搜寻顺序是:

values-xhdpi-720x1280(1080x1920 不会访问)

values-xhdpi-480x800

values-xhdpi-320x480(到底)

values-xh

values-xxh

values-xxxh(到顶)

values-h

values-m

values-720x1280(1080x1920 不会访问)

values-480x800

values-320x480(到底)

values

values-l

如果还没找到则报错

====================================

本文标签: android ppi 对应资源 Android适配原则及资源加载规则