开闭原则--可变与不变的分离,且容易定制
应用程序的目的是尽可能做到适用于多种设备,这些设备的配置不尽相同,有些不同的物理尺寸,分辨率.为了达到最佳的适配效果,和最少的代码重复,以及最好的可扩展性,就需要分离资源的使用和资源.用一个统一的资源管理者来管理资源.代码通过资源管理者提供的统一的接口来获取资源.这样对于使用者来讲资源的获取的方式是统一,资源者无需关心如何为不同的设备获取不同的资源.这样就把随不同设备变化而变化的代码降到最低,只有资源管理者需要操心不同的设备相关的不同的资源.
比如:字串的获取和使用.最直接的方式就是直接在代码中使用字面常量.但处理多语言时,这就非常的麻烦,需要代码去判断当前系统语言,然后决定使用哪个!但如果使用资源管理器来获取字串,就把代码耦合降到最低:只有资源管理器需要判断当前系统语言,然后去去相应的字串.其他人则无需要关心.
Android当中也是这样子的,代码通过统一的接口获取系统资源,而由资源管理器来处理设备相关性的问题.
Android的资源里面常见的hdpi等这都是什么意思呢?要理解这些,先要了解一下标准的Graphic Design中的一些度量和尺寸的
基本的术语和概念:
屏幕尺寸 Screen size
是指屏幕的物理尺寸,度量的方式是以屏幕对角线的长度,单位是英寸(1英寸=2.54厘米).
屏幕尺寸的大与小,通常能决定屏幕究竟能显示多少内容,比如电脑上能显示的区域一般都要比手机要多.
分辨率Resolution
通常以像素为单位,像素的定义为设备能显示的基本单元,比如480x800,或者1024x768,这意思就是说这个屏幕在长度上能显示1024这么多个点,在高度上能显示768个点.分辨率通常用于表示图像的尺寸.但是分辨率不能代表图像的真实物理尺寸.也不能决定在屏幕上显示的物理尺寸.
屏幕密度Density
是指这个屏幕在单位长度上能显示的像素点数,也就是一英寸上能显示的像素点数,计算方式就是分辨率除以物理尺寸.屏幕密度能反应设备的清晰程度.单位通常是PPI(Pixels Per Inch). PPI值越高,证明屏幕的清晰程度越好,能显示的越清楚.比如苹果的Retina系列屏幕,达到了326PPI.人眼的识别能力在300PPI左右,所以达到326时,人眼就无法分辨出像素点了,所以它显示的就更清晰.
有了屏幕密度的概念就可以看到,同样尺寸(分辨率)的图片在高密度的屏幕上显示的物理尺寸就会小(看起来小了),在低密度的屏幕上显示的物理尺寸就会变大(看起来大了).
所以为什么,没有为MBP视网膜屏做适配的软件,比如PS的图标会看起来模糊.因为MBP视网膜屏幕的密度大大提高,是以前的4倍,所以同样尺寸的图片看起来就会比原来小,是原来的1/4,太小了会看不见的,但是这样使用起来是很不方便的.所以Mac系统为了适应新的视网膜屏幕,就必须把图片进行缩放,放大原来的四倍,这样在用户眼中"看起来"(物理尺寸)图片还是那么大!因为图标被拉大了四倍,当然会模糊了!所以,Mac上面的应用,在视网膜之后必须对应用进行专门的优化和适配.
屏幕的长宽比
物理长度与物理宽度的比例.
如何适配不同的屏幕
适配不同的屏幕的目的也就是让应用在所有的系统上体验一致. 这个不同的平台有不同的策略.有些平台仅是以屏幕分辨率为依据.也就是把设备以屏幕分辨率为分类依据.这也是平常所见的VGA一类的名字的来源:
根据不同的屏幕分辨率来区分不同的设备,比如一些常见的名字:
QVGA: 240*320 Quarter VGA 四分之一的意思(1/2 * 1/2 = 1/4)
HVGA: 320*480 Half size VGA 的意思 二分之一(1/2 * 1 = 1/2)
VGA: 480*640 远古时代的电脑显示器的分辨率,其他的都是以此为基础来缩放
WVGA: 480*800 Wide VGA
SVGA: 600*800 Super VGA
等等,关于分辨率的区分可以参考维基百科:Graphics display resolution
那么,有一些的GUI系统就是以分辨率方式来区分不同的资源.
但是,如前面所述,分辨率不能代表屏幕的清晰程度.同样分辨率的图片,或者同样的长度在高密度的屏幕上看起来就会小,在低密度的屏幕上就会变大!因为它的单位是像素.所以,如果以分辨率作为依据来区分资源,那么就必须为所有不同的分辨率设定资源,并且做适配的优化,否则就不会得到好的显示效果!而想一想这工作量会多大!
屏幕密度无关的单位长度
适配不同的屏幕的目的是什么 呢?就是为了能让
1.应用在不同的平台都能正常的显示,也就是把该显示的显示出来,不能太大(低密度上)也不能太小(高密度上)
2. 排版等能够一致.也就是说元素在大尺寸屏幕上(高分辨率)上要大些,在小尺寸屏幕(低分辨率)上要小些
做为懒惰的人类的目的是:用最小的工作量来适配最多的屏幕,那么怎么做到呢?如果能有一个度量单位能随屏幕变化而变化,那多好啊,这样就可以指定一个长度,让设备自己去处理变化的因素.
所以,就出来了一个新的长度单位DIP---Density Independent Pixels,也就是密度无关的一个抽象的单位长度.它不像像素或者Inch之类的固定不变,而是随着设备的密度而变化的.在低密度的设备上它的值很小,而在高密度的屏幕上它的值会变得大些,这样一来,开发者就可以指定一个长度从而适应不同密度的屏幕,以解决低密度屏幕变大,高密度屏幕变小的问题.详细的来讲:比如某一窗口的长度是100DIP,在低密度的屏幕上DIP值,假如说是1pixel,那么长度就变成了100 pixels;而到了高密度屏幕上,DIP可能会变成了1.5
pixel,这样长度就变成了150pixels. 这样就达到了,以不变应万变的目的.而DIP具体取什么值,也是由设备来给出,因为设备知道它自己是什么样的密度.
Android上的适配策略
Android上面如何解决适配不同尺寸(分辨率)和密度的问题呢?它主要是通过以密度分类,再加上分辨率的方式来减化适配不同尺寸屏幕的工作量.
一般来讲,屏幕分辨率越高,清晰度也应该越高,也即其密度也应该越大,否则会看起来很不清楚,比如4寸的屏幕只显示100个像素,这就近距离看电影,或者看投影仪一样,非常的粗糙和不清晰.所以,Android主要是以屏幕密度来区分不同的设备:
高密度: hdpi (High dots per inch)
中等密度: mdpi (Medium dots per inch)
低密度: ldpi (Low dots per inch)
并且布局中推荐使用密度无关单位dip或dp,来作为长度或者宽度的单位.这样,从理论上来讲,开发者只需要做:
1. 为不同的密度屏幕准备图片资源
(图片是没办法的,因为图片的长度和宽度是固定的像素值,不能够随密度变化而变化,可以强行拉伸,但图片会失真.当然也有9 Patch图片可以解决随意拉伸的问题.但普通的图片的长度和宽度是固定的.
2. 用dip作为单位来指定长度或者宽度
就可以适配所有的设备,让布局在所有的屏幕上都得到比较好的显示效果.
当然,现实的生活没有这么完美,各种设备千差万别.但是总体仍可分为这三大类,为这三大类准备好图片后,其他的只要与某一类较接近,即使稍有拉伸或失真,也不太明显,是可以接受的.所以,对于一般性的应用程序,写一个布局文件在layout中,为三种密度准备图片drawable-hdpi, drawable-mdpi, drawable-ldpi,就足以应对80%的设备.
res/
drawable-hdpi/
ic_launcher.png
drawable-mdpi/
ic_launcher.png
drawable-ldpi/
ic_launcher.png
layout/
main.xml
(这里可能有点过时了,因为现在多了xdpi,而且很多设备也是xdpi的.)
但是光以密度屏幕来分类和处理还不够.随着设备的越来越多,以及屏幕尺寸越来越大,还有就是Tablet的出现,又会出现这样的问题:设备的屏幕密度虽然不高,但其分辨率很高.举个简单的例子:iPad2的分辨率是1024x768,iPhone 4 960x640,但是iPhone 4的密度是326ppi,远大于iPad2.但是,无论密度有多高它的屏幕就那么,最多能显示960x640个像素点,一个1024*768的图片在iPad上可以看到全部,而iPhone上只能看到一大半!这也是为什么用iPad来运行iPhone上的应用程序时,只是以屏幕中间的一部分来模拟显示的原因.
对Android来说也是一样的.如此一来,即使相同的dpi,假如其屏幕尺寸非常大,那么为其准备的图片将被拉伸很大或者显示不全.UI元素也会被拉伸很长.这样并不是很好的体验.对于尺寸大的屏幕应该让其显示更多的内容,而不是把一部分元素拉伸很大.所以,很多手机安卓应用如果未经专门适配,在平板上直接使用体验将会是非常差的.
为了解决这样的问题,就还必须以屏幕尺寸来区分设备
主要有四种屏幕尺寸:small, normal, large and xlarge
这主要是配合屏幕密度来一起使用,比如,适配平板的图片:
drawable-xlarge-hdpi/ic_launcher.png
这里就要提到了密度,尺寸和分辨率的对应关系了. 屏幕分辨率是随设备变化最明显的一个,上面的二种分类方法仅是对屏幕进行的大致的一个分类.虽然屏幕分辨率与密度没有直接的关系,但是所有的设备都基本上一致的(约定俗成?):
ldpi QVGA 240*320 0.8
mdpi HVGA 320*480 1.0
hdpi WVGA 480*800 1.5
hdpi qHD 540*960 1.5
xdpi WXGA 720*1280 2
对于,如何适配,以及如何提供资源可以阅读官方文档,里面讲的还算详细.http://developer.android.com/guide/topics/resources/providing-resources.html
适配不同屏幕的常见问题
虽然有了上述的策略的方式,但是在实际中还是不够的.因为屏幕尺寸的比例与密度的比例并不一致.举例来说HVGA与WVGA相比,宽度的比例与密度的比例是一致的480/320=1.5.所以说宽度上显示的内容一样多.无需操心.但是高度上面就不一样了.800/480 >1.5,这就造成了什么情况呢,也就是同样的东西缩放了1.5倍以后,对于WVGA还会有相当的屏幕空着.或者如果以WVGA为基准时,放到HVGA上就会显示不全. 在qHD上这个问题会更明显.甚至同样的hdpi的WVGA和qHD都会有问题,qHD能显示的内容要多于WVGA.
使用dimension资源来解决问题
从上面的问题可以看出,密度无关的单位解决不了上面的问题.当然也可以选择为每一分辨率做一布局,但这样又会造成大量的重复工作,因为毕竟布局是相同的,仅是元素的高度或者宽度需要考虑分辨率和屏幕密度.
这时可以把高度或者宽度抽象出来放到一个单独 的资源dimensions中,从而把变化的东西降到最小:
比如某个View的高度:
layout/
main.xml # android:layout_height="@dimen/view_height"
values-mdpi
dimensions.xml item name="view_height">20dip</item>
values-hdpi
dimensions.xml 30dip
values-hdpi-960x540
dimensions.xml 40dip
还有一点就是,尽可能用相对的值,如wrap_content和fill_parent(or match_parent)它们不是固定的值而是会在具体布局的时候计算出来.
最后,分享一个坑,资源的默认值并不总是values,drawable和layout. 当你分别在values中指定一个值和在values-mdpi中指定一个值,对于其他的类型如hdpi会使用mdpi中的值,而非values中的值.
另外就是,做图片时需要注意尺寸问题
因为图片的尺寸是以像素为单位,而Android应用程序中多以dip或dp为单位,所以就要注意不同密度上它们的对应关系.让像素仁值和用dip换算过后都是整数.举例来讲HVGA上或者mdpi上1个dip就等于1个像素.但到了WVGA或者hdpi上1个dip就是1.5个像素,所以对于WVGA的图片的尺寸的像素值最好能是1.5的倍数.这样就更方便用dip来定义图片的长度的宽度.
其他有用的资料:
1. Support different Android device configurations with
dimension resources
3. 适配特定的分辨率