前言
安卓是一个只对硬件设备限制有很少限制的移动操作系统。生产商们几乎可以创造任何形状的、尺寸的和密度的屏幕的设备。设备可以有物理键盘和按钮或者只有虚 拟键盘和按钮。由于它的设备客制化的*性给软件开发者们制造了一些麻烦。首先,应用软件怎样在各种各样的设备上保证一致的用户体验呢?其次,应用软件怎 样利用一些拥有高端硬件或者独特特性的设备的优势呢?安卓在当初研发的时候就考虑了这些,给开发者提供了一些工具去支持各种配置的设备,优化了不同配置的 设备的用户体验,这些将在下面介绍。
为了应用程序的灵活性和兼容任何配置的设备,认真思考合适的跨配置的用户体验是必须的。创建一个Android应用程序时,设计者和开发者必须创建一个无 论是在小尺寸的手机上还是在大尺寸的平板上都能运行良好的UI界面。他们还要考虑图片资源对高密度和低密度的屏幕的优化。这篇BLOG的中心就是安卓怎样 支持不同密度的屏幕。
安卓的基本设计思想就是保证用户界面中的元素拥有相同的物理大小,而不论屏幕的密度。为什么呢?很简单,无论什么样的屏幕密度一个用户的手指的物理尺寸是 不变的。按键或者可以按的元素在任何设备上应该渲染成和手指物理大小一样尺寸(比手指印大一点)。文本和字母在不同的设备上也应该渲染成一样的字体大小 (可读的)。
屏幕密度不等于分辨率
屏幕像素密度是分辨率和显示尺寸的比值,可以用每英寸像素点或者dpi来度量。dpi越高,每个像素点越小也就越清晰。简单地说,dpi越高就意味着每英 寸显示的细节越多,而不是直接取决于高分辨。举个例子,Galaxy Nexus(对角线 4.65'')分辨率为:720x1280,Nexus 7(对角线 7'')分辨率为:800x1280.常见的错误观点是认为他们拥有相同的屏幕像素密度,因为它们的分辨率几乎一样。然而,Galaxy Nexus 的屏幕像素密度约为316dpi,Nexus 7的屏幕像素密度为216dpi,相差甚远。这是因为虽然它们拥有一样的分辨率,但是它们显示尺寸却不一样。再次说明,屏幕像素密度是分辨率和显示尺寸的 比值,两个因素一起决定屏幕像素密度。
密度级别
无数的Android设备的像数密度各不相同,分布为100dpi到480dpi以上。为那些不同密度的屏幕优化图片,就必须创建不同分辨率的图片。然 而,尝试为所有的像数密度优化所有的图片资源将会使是让人无法想象的无聊乏味,同时引起应用程序大小变的臃肿,简单的方法是不可行的。作为折衷方 案,Android用密度级别的概念把拥有确定密度范围的设备分类。这样应用程序只需要针对每个密度级别来优化图片,而不是针对所有可能的像素密度来优 化。这使得设计者和开发者的工作负担是合理的,时阻止了应用程序大小的膨胀。当然,代价就是:不同像素密度的设备上图片渲染后的物理尺寸是大小不一的,这 点将会在后面介绍。
因此,设计者和开发者如何根据密度块来优化图片资源?首先,要决定的是图片的呈现尺寸。例如,一个图标试图在屏幕中呈现 0.5x0.5的大小。下一步,创建一个图片支持的最大密度,或者是一个可伸缩的矢量图。最好的做法是支持最大的密度,当前是在xxhdpi 就在480dpi.在480dpi,一张0.5x0.5的图片转换为240x240px.一旦图形资源的最大密度版本被创建在240x240px.那么它 就可以在随后创建的密度块版本被适当的缩小比例,每个版本用相同的文件名。Google建议不要在213dpi创建tvdpi版本,因为它仅仅是被特定的 应用程序所需要,并且被一小撮设备而使用。一旦所有的版本被创建,它们可以被添加到drawable文件夹,并且用资源标识符告诉Android系统每一 种密度块所需要的大小。最后,参考一下在xml布局层的图形资源,然后通过编码它们的名称在R文件中生成,R文件是一个可以在程序中引用所有资源的文件。 Android系统会在运行时加载资源,并尽它最大的努力去匹配实际的设备配置与资源标识符应用。假如任何图片资源的密度版本没有被包括在 内,Android将用另外的密度版本并且将它缩小到所需要的合适的尺寸大小。当然,不推荐让Android系统来这样做。因为Andorid系统在操作 图片资源时并没有图片编辑软件操作的更加有 效和精确。
尺寸单位
在安卓中,用户界面可以在xml文件中创建或在在代码中实现。有几种单位可以用来表示一个表单的长宽。它们可以用在很多元素上来设定宽、高、外间距、内间距等等。
px——屏幕上真实的像素。这是一个与像素密度有关联的单位,一px单位的物理大小取决于屏幕的像素密度。
in——屏幕上的物理英寸。这是一个与像素密度无关联的单位,一in单位的物理大小在任何像素密度的屏幕上都是一样大的。一in单位转化为多少px单位取决于屏幕的像素密度。
mm——屏幕上的物理毫米。这是一个与像素密度无关联的单位,一mm单位的物理大小在任何像素密度的屏幕上都是一样大的。25.4mm等于一in。一mm单位转化为多少px单位取决于屏幕的像素密度。
pt——点,屏幕上普通字体大小单位。这是一个与像素密度无关联的单位,一pt单位的物理大小在任何像素密度的屏幕上都是一样大的。72pt等于一in。一pt单位转化为多少px单位取决于屏幕的像素密度。
dp——像素密度无关联的像素单位。这是一个与像素密度无关联的单位。然而一dp单位的物理大小在不同的像素密度屏幕上只是近视的相等。大约160dp等于一in。在一dp转化为160dpi中的一个比例因子是与设备的密度级别相关联的。一dp等于多少像素取决于屏幕的像素密度和设备所属的密度级别。
sp——大小独立的像素单位,特地指定text的大小。这是一个与像素密度无关联的单位。然而一sp单位的物理大小在不同的像素密度屏幕上只是近视的相等。在一sp转化为160dpi中的一个比例因子是与设备的密度级别以及字体表现的大小相关联的。一sp等于多少像素取决于屏幕的像素密度和设备所属的密度级别。
神奇的“dp”
如前面讨论的那样,"px"不是密度独立的,并且在不同的设备上拥有不同的大小,然而"in","mm"和"pt"是密度独立的,当然在不同的设备上拥有相同的大小。但是,"dp"和"sp"跟其余的有一些不同,虽然他们是密度独立的,但是他们在 不同的设备上拥有不同的大小。这是为什么呢?答案是"dp"和"sp"是如何被计算成像素的。Android使用mdpi(160dpi)作为其标准密 度,这里 1dp就是1px。本质上来讲,"dp"可以被认为是在160 dpi上的“px”。这就是为什么160 dp转换为大约1 in。因此根据设备密度等级与标准的密度(mdpi)之间的比率来转换"dp"到"px"。
"dp"趋向于不同的物理大小的原因是由于相同的缩放因子被应用到整个密度桶。该缩放因子是计算密度桶dpi,而不是设备真实的 dpi。当设备的dpi不完全地与它的密度桶dpi相同的时候,相同数目的"dp"转换为相同量的"px"。这就会导致相同数目的"px"被显示到不同密 度的屏幕上,以不同的大小呈现出来。
上面的表格显示了100 dp如何在不同密度的设备上转换为"px"。100 dp应该大致地转化为0.625 in,并且完美地转换为bucket sizes。但是,当设备的dpi少于密度桶的dpi的时候,它的像素在物理上会更大,并且相同数目的"dp"会渲染得更大,反之亦然。
因此这就引出一个问题,为什么"dp"允许这种物理尺寸的变化?从根本上来说,android在物理尺寸上会牺牲一些精确度,目的是为了保持性能和显示质 量。由于利用android的密度桶比率(0.75:1.0:1.5:2.0:3.0)让"dp"缩放到"px",这就允许有极小的"px"舍入和简单的 估计。同时,由于缩放因子与密度桶比率成比例,“dp”会按比例显示为各种密度提供的图片资源。最后,当缩放图行的时候,最好保持接近整数和简单的分数,因为复杂的分数会导致图片颜色过渡异常和走样。
定义UI元素的界限
当定义用户界面元素的长宽时有几个特别的选项可供选择,就像尺寸单位一样。
wrap_content——这个选项将会扩展元素的边界到足够的大小以致可以容纳它包含的内容(图片,文本等等),本质上,这个选项把元素设置成它最大子元素的大小,不会去调整元素的大小。
match_parent(fill_parent 在API 8中已过时)——这个选项会使元素适应父元素的大小,使用父元素最大的空间,减少间距。本质上,这使子元素为父元素允许的最大尺寸的大小,并且必要的时候会调整元素的大小。
dimension unit——这个选项设置元素的边界为精确地单位大小,这些单位上文已经讨论过。本质上,它会设置元素的边界为确定的单位大小,必要的时候会调整元素的大小。
Demo
为了解释这一切是如何一起工作的,创建并测试一个示例应用程序是必要的。例如,采用一个在密度为xhdpi或320dpi,200X200 px的图片的设计。简单起见,将图片资源命名为“android_logo”。利用密度在320dpi,200X200 px图像分辨率作为所需的物理尺寸,可替换的图像尺寸可以被计算出来。
密度为xhdpi (320dpi),200x200px 的图片
在计算完所需特定密度的图像尺寸后,他们能够在最大密度版本被缩小到相应的尺寸,如前面所解释的那样。接下来,每一个密度版本的图片,能够被放入到用相应资源标识符标记的“drawable”文件夹下。Android系统会根据设备上的配置,在运行时选择最佳的资源。
有标识符的资源目录
现在,为了呈现保持密度独立的可能方式,利用每一种密度独立的尺寸单位,将该图片设置成相同的物理尺寸。因为"px"不是密度独立的,而"sp"是为text设计的,因此忽略它们。
设备例子:xhdpi Bucket
通过使用一个布局,里面包含相同的图片,这些图片是利用各种尺寸单位设置相同的物理大小,它可以比较每个尺寸单位是如何工作的。当运行在320 dpi的模拟器上,图片会以完全相同的大小显示,每一个尺寸单位都是这样。这是意料之中的,因为模拟器的dpi完全匹配xhdpi(320 dpi)密度等级。但是,当运行在同样xhdpi 密度等级的Galaxy Nexus上时,图片的大小尺寸会有一些变化。
320dpi 模拟器截图
Galaxy Nexus 截图
经过检查发现,很显然用"wrap_content"和"dp"设置的图片在大小上是匹配的,与此同时,用"in","mm"和"pt"也是匹配的。但 是,这两组没有彼此匹配。到底发生了什么事情呢?如之前讨论,"wrap_content"和"dp"利用密度桶比率去适当地转换它们的尺寸。因此,图片 大小被计算到200x200 px。但是,Galaxy Nexus的实际屏幕密度并不是320 dpi,它是 315.3 xdpi和 318.7 ydpi。 因为实际的密度低于320 dpi的xhdpi密度,因此在实际设备上的像素点会比想象的0.625 in的物理大小更大。
当分析用"in","mm"和"pt"设置图片大小的时候,注意到每一个图片被转换为 197.1x199.2 px。如之前讨论,"in","mm"和"pt"都是利用实际设备的密度去转换它们的尺寸到像素。因为Galaxy Nexus的实际密度低于320 dpi的xhdpi密度,更少的像素需要被转换为0.625x0.625 in,这样图片就被缩小了。
当对所显示的图片进行对比时,很明显,用"wrap_content"和"dp"设置的图片物理尺寸精确度较低,但是它们的图片质量比"in","mm"和"pt"更好。
没有缩放"wrap_content"和"dp"(左)。有缩放"in","mm"和"pt"(右)
设备例子: hdpi Bucket
下一步,当运行在240dpi的模拟器上时,图片以完全相同的大小显示每一个尺寸单元。重复一下,之所以期望这样是由于模拟器的dpi在240dpi的条 件下恰好匹配hdpi密度的桶(bucket)。但像以前一样,当运行于hdpi设备时,出现一个不可思议的事情,图像的尺寸有一些变化。
240dpi模拟器屏幕截图
HTC Droid Incredible截图
像往常一样,HTC Droid Incredible显示150x150 px的图片,该图片用"wrap_content" 和"dp"设置。因为Droid Incredible的实际屏幕密度为254x254dpi,大于240dpi的hdpi密度,这意味着在屏幕上的像素小于240 dpi密度对应像素的大小。这就会导致150x150 px的图片显示要小于0.625 in的基础大小。用"in","mm"和"pt"设置的图片显示为158.8x158.8px,因为需要更多的像素点转换为0.625x0.625 in,这样该图片就会被放大。
就像在Galaxy Nexus上,用"wrap_content"和"dp"设置的图片物理尺寸精确度较低,但是它们的图片质量比"in","mm"和"pt"更好。
没有缩放"wrap_content"和"dp"(左)。有缩放"in","mm"和"pt"(右)
你能在GitHub找到该demo所有的源码,或从后面的附件中获得。
尺寸单位最佳实践
px- 没有保持密度独立。应该永远不需要。
in/mm/pt- 保持密度独立,但是计算到确切数额的像素,会降低体验,还会导致图片颜色过渡不自然和走样。如果需要尺寸大小的精确度,并且任何偏差都是不可接受的,那选择in/mm/pt就是必要的。需要确切距离的时候,设置元素间的距离,in/mm/pt也是很有用的。
dp- 保持密度独立并且保持最佳图片质量,但是要在物理大小上,以小的偏差作为代价。因为它利用密度桶比率来进行计算大小,它适当地提供准确的尺寸与图片资源。这是被推荐使用的尺寸单位,用来设置元素间的界限或者之间的距离。
sp- 保持密度独立和字体的质量,但是要在物理大小上,以小的偏差作为代价。这是被推荐使用的尺寸单位,仅仅用来设置字体大小,因为它需要考虑密度和用户的文本大小偏好。
总结
总的来说,保持密度独立性的要点:
- 确定每一个图像资源确定呈现该资源所需的物理尺寸
- 设计尺寸比最大密度桶更大的矢量图像资源或原始图像资源
- 为每一个密度桶创建特定的density 版本,然后将他们放入用合适标识符标识的“drawable”资源目录下
- 当设置图像边界的时候,用“wrap_content”来获得最佳显示效果,用“match_parent”来充满整个显示区域,或者用“dp”来设定一个固定的尺寸
- 当在布局里面设定距离的时候,用“dp”获得最佳显示效果。如果需要使用精确的尺寸,仅用“in”,"mm"或"pt"。在这里永远别使用"px"
随着对Android如何处理在不同密度的屏幕上显示的理解,这将使得在任何密度的显示屏上设计和开发最佳的应用程序变得更加容易。