题记:android设备多样化,要想程序在多个设备上运行看起来都不走样,需要考虑到不同屏幕的展示效果差异性。本篇主要是学习SDK中支持多屏幕资料的一个笔记。

主要内容:

  • 基础概念  
  • 具体从哪几方面考虑支持多屏幕
  • 如何更好的支持平板
  • 最佳实践,设计时要注意的方面
  • dp相关深入了解

一、基础概念

  1. 屏幕大小(Screen Size)
    设备的屏幕物理大小,比如3'',7'',10''等。在API版本13之前(3.2),屏幕被分成四大组:small,normal,large,xlarge。但是在13往后,可以支持更加精确的屏幕区分:sw600dp,sw720dp,w600dp等。
  2. 屏幕密度(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的。
  3. orientation
    屏幕的方向,主要分为两种:landscape或者portait,程序运行的时候该值有可能被动态改变。
  4. 分辨率(resolution)
    屏幕上物理像素个数,不能硬编码设置该值,因为不同屏幕上,像素值有很大的差别,若是硬编码控件单位为像素,那么在不同设备上面,显示效果会有很大的差别。比如,一个在mdpi下显示正常的控件,会在ldpi屏幕下显示得过大,在hdpi屏幕下显示得过小。总之不能正确的显示出想要显示的效果~那么按这么说,分辨率不是没用了吗?实际上,系统都是将dp默认转化成pix的,只不过根据屏幕密度以及比例系数进行转化,使得不同设备上面,看起来控件不会差别太大。
  5. 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的。

  具体来说,包括以下几点:

  1. 通过使用configuration qualifiers来区分不同情况下的资源,系统会通过最佳适配的方法在展现时,查找最合适的资源来展现。SDK说,在适配的时候,如果没有找到合适的资源,会偏向选取较小的资源。若是当前较小的也不存在,所有已存的资源要比当前屏幕尺寸大,那么将会导致系统崩溃。这个就是说明了一种情况,高分辨率的程序不一定能在小屏幕上面运行。
    还有一个比较关键的,不需要针对每种情况都做一个相应的配置,如何合理的通过配置来支持多屏幕。
  2. 提供灵活的布局。
    布局嘛,主要是考虑到屏幕大小的不同,界面上对应的控件布局也需要相应的做调整。如在小屏幕上面,一排放不下的按钮在大屏幕上面就能放下,还有空余。
    针对small的屏幕,要能够做到正常显示内容;
    针对大屏幕,要做到能够合理利用剩余空间;
    还要考虑到横竖屏切换时,相应的控件布局是否需要调整。
  3. 针对四种屏幕密度提供相应的图片资源。
    长宽比都是一样: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)。

四、最佳实践,设计时需要考虑到的方面

  主要包括以下几个方面:

  1. 在布局文件中尽量使用wrap_content,fill_parent以及dp;
  2. 不要在代码中,使用像素的硬编码,因而不同屏幕统一控件可能所获取的宽度不一致;
  3. 不要使用绝对布局(AbsoluteLayout),替换的来说,可以使用相对布局;
  4. 提供多种布局和资源。  

五、dp相关深入了解

  当需要更深一步操作图片时,就需要了解系统是如何处理图片以及缩放的。

  1. pre-scalling
    根据现有的屏幕密度来自动更改图片资源大小,以达到适应屏幕的目的。程序里面获取的展示图片大小实际上是已经缩放过的图片大小,比如一个图片在mdpi下是24x24 px的,而没有提供针对hdpi的图片,那么系统就会自动的将该图片扩大为32x32px。
  2. auto-scalling
    会把屏幕当前分辨率转化成mdpi下的分辨率。在3.0以后和pre-scalling没有明显的差别。

  二者区别:前者是在图片展示出来之前做的动作,会比后者消耗更多的内存;后者是在draw的时调整的大小,比前者消耗更多的cpu.另外,当在内存中动态创建图片时,系统默认认为是mdpi的,然后会根据当前屏幕密度来自动伸缩图片大小。

  本文地址:http://www.cnblogs.com/caiwan/archive/2013/02/05/2893234.html

posted on 2013-02-05 17:27  西瓜瓜瓜瓜瓜  阅读(594)  评论(0编辑  收藏  举报