zoukankan      html  css  js  c++  java
  • UI之支持多屏幕

    题记: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
      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

    看不清未来,那就看脚下。
  • 相关阅读:
    循环获取数据
    implode
    获取二维数组中的值
    根据id获取某一类的最大最小值
    array_column的作用
    用curl模拟夹带cookie的http请求
    phpunit——执行测试文件和测试文件中的某一个函数
    call_user_func
    9 [面向对象]-内置方法
    8 [面向对象]-反射
  • 原文地址:https://www.cnblogs.com/caiwan/p/2893234.html
Copyright © 2011-2022 走看看