题记:android设备多样化,要想程序在多个设备上运行看起来都不走样,需要考虑到不同屏幕的展示效果差异性。本篇主要是学习SDK中支持多屏幕资料的一个笔记。
主要内容:
- 基础概念
- 具体从哪几方面考虑支持多屏幕
- 如何更好的支持平板
- 最佳实践,设计时要注意的方面
- dp相关深入了解
一、基础概念
- 屏幕大小(Screen Size)
设备的屏幕物理大小,比如3'',7'',10''等。在API版本13之前(3.2),屏幕被分成四大组:small,normal,large,xlarge。但是在13往后,可以支持更加精确的屏幕区分:sw600dp,sw720dp,w600dp等。 - 屏幕密度(Screen Density)
屏幕密度是指每英寸屏幕所占的像素点个数,密度越高,单位像素也就越多。和屏幕大小是相互独立的概念,单位为dpi。密度主要分为四组:low,medium,high,xhigh。四种标志性密度的标准分别是:120dpi,160dpi,240dpi,320dpi.比例分别为3:4:6:8。那么拿到一个设备,如何知道也就是计算它的屏幕密度呢?假设现有一设备,为7‘’,分辨率是1024x800,它的屏幕密度如下:
dpi=sqrt(10242+8002)/7≈186dpi,那么这个186dpi属于哪个档次的屏幕呢?虽然说sdk中给出了一个大概的范围,但是如何才能精确到具体的值呢?经过一番学习,经过如下:
首先,可以通过如下方式获取android系统自动获取的设备屏幕密度:
1 DisplayMetrics metrics=new DisplayMetrics(); 2 getWindowManager().getDefaultDisplay().getMetrics(metrics); 3 TextView tv=(TextView)findViewById(R.id.tv); 4 tv.setText("分辨率:"+metrics.widthPixels+"x"+metrics.heightPixels+"\n density:"+metrics.density+"\n dpi:"+metrics.densityDpi);
用自己的华为荣耀U8860测试,测试结果如下:
分辨率:480x854 density:1.5 dpi:240
但是实际通过上述计算公式能得到它的屏幕密度约为245dpi,那多余的5dpi为何没有了呢?是否android系统在获取该密度的时候还经过了其它的额外处理?
第二,查看display类以及DisplayMetrics类源码发现,在DisplayMetrics类中,通过如下方法获取的屏幕密度:
1 private static int getDeviceDensity() { 2 // qemu.sf.lcd_density can be used to override ro.sf.lcd_density 3 // when running in the emulator, allowing for dynamic configurations. 4 // The reason for this is that ro.sf.lcd_density is write-once and is 5 // set by the init process when it parses build.prop before anything else. 6 return SystemProperties.getInt("qemu.sf.lcd_density", 7 SystemProperties.getInt("ro.sf.lcd_density", DENSITY_DEFAULT)); 8 }
也就是说是读取的系统配置文件里面的值,那是否是在系统安装的时候,该值已经默认写入了?再查找与关键字“ro.sf.lcd_density”相关的各种资料,最终在android\external\qemu\android文件夹下的hw-lcd.c文件中有了说明,启发来自安卓中文网的一个开发教程,翻看本机的源码如下:
1 hwLcd_setBootProperty(int density) 2 { 3 char temp[8]; 4 5 /* Map density to one of our five bucket values. 6 The TV density is a bit particular (and not actually a bucket 7 value) so we do only exact match on it. 8 */ 9 if (density != LCD_DENSITY_TVDPI) { 10 if (density < (LCD_DENSITY_LDPI + LCD_DENSITY_MDPI)/2) 11 density = LCD_DENSITY_LDPI; 12 else if (density < (LCD_DENSITY_MDPI + LCD_DENSITY_HDPI)/2) 13 density = LCD_DENSITY_MDPI; 14 else if (density < (LCD_DENSITY_HDPI + LCD_DENSITY_XHDPI)/2) 15 density = LCD_DENSITY_HDPI; 16 else 17 density = LCD_DENSITY_XHDPI; 18 } 19 20 snprintf(temp, sizeof temp, "%d", density); 21 boot_property_add("qemu.sf.lcd_density", temp); 22 }
该方法对“qemu.sf.lcd_density”设置了初始值,然后每次getDeviceDensity方法调用的时候都获取这个初始化值,这就能够很好的解释为啥算出来是244dpi,结果系统获取的却是240dpi了。默认是将密度值向四种类型上面靠,只要不是这四种,都会自动转化的。也就是说,可以得到一个范围:
low:小于(120dpi+160dpi)/2=140dpi的屏幕密度都是120dpi,
medium:大于等于140dpi小于(160dpi+240dpi)/2=200dpi的屏幕密度都是160dpi,
high:大于等于200dpi小于(240dpi+320dpi)/2=280dpi的屏幕密度都是240dpi,
xhigh:大于等于280dpi的屏幕密度都是320dpi.
再回到最原始计算出来的188dpi,实际上系统处理时,该屏幕属于medium的。 - orientation
屏幕的方向,主要分为两种:landscape或者portait,程序运行的时候该值有可能被动态改变。 - 分辨率(resolution)
屏幕上物理像素个数,不能硬编码设置该值,因为不同屏幕上,像素值有很大的差别,若是硬编码控件单位为像素,那么在不同设备上面,显示效果会有很大的差别。比如,一个在mdpi下显示正常的控件,会在ldpi屏幕下显示得过大,在hdpi屏幕下显示得过小。总之不能正确的显示出想要显示的效果~那么按这么说,分辨率不是没用了吗?实际上,系统都是将dp默认转化成pix的,只不过根据屏幕密度以及比例系数进行转化,使得不同设备上面,看起来控件不会差别太大。 - Density-independent pixel(dp)
屏幕独立像素,一般使用该单位来设置控件的大小,它能屏蔽设备的差异性。android系统在展示控件的时候,会将该值转化成pix,dp和px之间转化有一个系数,根据屏幕密度/160得出来的,在四种密度下,系数分别是:0.75,1.0,1.5,2.0。也就是说,宽100dp的控件,分别在四种屏幕上,所占的实际像素宽度为75px,100px,150px,200px。一个是主要用在图片资源的判断上面,还有一个是设置layout布局。
二、从哪几方面来考虑支持多屏幕
其实系统已经做了大部分屏幕自适应的工作,在不同的屏幕上面,通过伸缩layout来适应屏幕大小和密度,调整bitmap大小来适应屏幕密度。但是还是需要从以下几方面来考虑:
- 在manifest文件中显示声明应用程序支持哪些屏幕
- 针对不同的屏幕尺寸提供不同的布局文件
在资源文件夹后面加上相关的qualifiers,如针对3.2以前的版本,支持layout-small,layout-normal,layout-large,layout-xlarge。但是这个的界定比较模糊 - 针对不同的屏幕密度提供不同的资源文件,default中存放的资源默认是mdpi的。
具体来说,包括以下几点:
- 通过使用configuration qualifiers来区分不同情况下的资源,系统会通过最佳适配的方法在展现时,查找最合适的资源来展现。SDK说,在适配的时候,如果没有找到合适的资源,会偏向选取较小的资源。若是当前较小的也不存在,所有已存的资源要比当前屏幕尺寸大,那么将会导致系统崩溃。这个就是说明了一种情况,高分辨率的程序不一定能在小屏幕上面运行。
还有一个比较关键的,不需要针对每种情况都做一个相应的配置,如何合理的通过配置来支持多屏幕。 - 提供灵活的布局。
布局嘛,主要是考虑到屏幕大小的不同,界面上对应的控件布局也需要相应的做调整。如在小屏幕上面,一排放不下的按钮在大屏幕上面就能放下,还有空余。
针对small的屏幕,要能够做到正常显示内容;
针对大屏幕,要做到能够合理利用剩余空间;
还要考虑到横竖屏切换时,相应的控件布局是否需要调整。 - 针对四种屏幕密度提供相应的图片资源。
长宽比都是一样:3:4:6:8,针对需要做控件背景的图片,需要弄成nine patch格式的。
三、更好的支持平板
由于3.2之前对屏幕的尺寸就只有那四类,有可能属于同一类的设备但是屏幕布局并不一定适用,因而需要使用新的size qualifiers。比如虽然7"和5"的都属于large范畴,但是,二者在布局上还是需要有所区别。主要新的size qualifiers有:
- smallestWidth :
sw<N>dp,就是屏幕的最小宽度,不考虑横竖屏切换,实际上就是把高和宽都看成宽度,取其最小值。使用该标志,能够区分出是哪个尺寸的平板,如7"的平板,分辨率为1024x600,密度为mdpi,可以定义成sw600dp,大于该值的是7"平板;10"的平板,分辨率是1280x720,可以定义成sw720dp,大于该值的是10"平板。 - avaliable screen width:
w<N>dp 也是屏幕的最小宽度,但是它考虑横竖屏切换,实际上呢,就是只计算横向的宽度,这个标志可以用来针对屏幕方向的变化而改变布局(横屏时,提供多panel,竖屏单个panel)。
四、最佳实践,设计时需要考虑到的方面
主要包括以下几个方面:
- 在布局文件中尽量使用wrap_content,fill_parent以及dp;
- 不要在代码中,使用像素的硬编码,因而不同屏幕统一控件可能所获取的宽度不一致;
- 不要使用绝对布局(AbsoluteLayout),替换的来说,可以使用相对布局;
- 提供多种布局和资源。
五、dp相关深入了解
当需要更深一步操作图片时,就需要了解系统是如何处理图片以及缩放的。
- pre-scalling
根据现有的屏幕密度来自动更改图片资源大小,以达到适应屏幕的目的。程序里面获取的展示图片大小实际上是已经缩放过的图片大小,比如一个图片在mdpi下是24x24 px的,而没有提供针对hdpi的图片,那么系统就会自动的将该图片扩大为32x32px。 - auto-scalling
会把屏幕当前分辨率转化成mdpi下的分辨率。在3.0以后和pre-scalling没有明显的差别。
二者区别:前者是在图片展示出来之前做的动作,会比后者消耗更多的内存;后者是在draw的时调整的大小,比前者消耗更多的cpu.另外,当在内存中动态创建图片时,系统默认认为是mdpi的,然后会根据当前屏幕密度来自动伸缩图片大小。
本文地址:http://www.cnblogs.com/caiwan/archive/2013/02/05/2893234.html