1. 概述
在平时工作的过程中,总是会遇到这样的问题,将一张图片放到drawable、drawable-ldpi、drawable-mdpi、drawable-hdpi、drawable-xhdpi、drawable-xxhdpi或者drawable-xxxhdpi目录中,在app运行时,加载到内存中的图片与资源目录中的图片相比尺寸可能被缩小或者放大,本篇文章就是来解析这个问题的原因。
2. 预备知识
2.1 术语和概念
Screen size(屏幕尺寸):按屏幕对角测量的实际物理尺寸。
为简便起见,Android 将所有实际屏幕尺寸分组为四种通用尺寸:small, normal, large, and extra-large。
Screen density(屏幕密度):屏幕物理区域中的像素量;通常称为dpi(dots per inch)。例如, 与“ normal”或“ high”密度屏幕相比,“ low”密度屏幕在给定物理区域的像素较少。
为简便起见,Android 将所有屏幕密度分组为六种通用密度: low, medium, high, extra-high, extra-extra-high, and extra-extra-extra-high。
Orientation(方向):从用户的角度来看屏幕的方向,即横屏还是竖屏。 请注意,默认情况下不仅不同的设备以不同的方向运行,而且当用户旋转设备时,方向可能会在运行时发生变化。
Resolution(分辨率):屏幕上物理像素的总数。添加对多种屏幕的支持时, 应用不会直接使用分辨率;而只应关注通用尺寸和密度组指定的屏幕尺寸及密度。
Density-independent pixel(密度无关像素 dp):在定义UI布局时应使用的虚拟像素单位,用于以密度无关的方式表示布局尺寸或位置。
一个密度无关像素等于 160 dpi 屏幕上的一个物理像素,“ medium”的屏幕密度为160 dpi。在运行时,系统会根据当前屏幕的实际密度按需要对资源进行适当的缩放 。dp单位转换为屏幕像素很简单: px = dp * (dpi / 160),例如,在 240 dpi 屏幕上,1dp等于 1.5 物理像素。在定义应用UI时应始终使用 dp 单位 ,以确保在不同密度的屏幕上显示UI的大小一致。
2.2 屏幕尺寸和屏幕密度的分类
从 Android 1.6(API 级别 4)开始,Android 支持多种屏幕尺寸和密度,反映设备可能具有的多种不同屏幕配置。 您可以通过Android系统的功能优化应用在各种屏幕配置下的用户界面 ,确保应用不仅正常渲染,而且在每个屏幕上提供最佳的用户体验。
为简化为多种屏幕设计用户界面的方式,Android 将实际屏幕尺寸和密度的范围分为:
1> 四种通用尺寸:small, normal, large, and xlarge
注意:从 Android 3.2(API 级别 13)开始,这些尺寸组已弃用,而采用根据可用屏幕宽度管理屏幕尺寸的新技术。如果为 Android 3.2 和更高版本开发,请参阅声明适用于 Android 3.2 的平板电脑布局以了解更多信息。
2> 六种通用的密度:
ldpi (low) ~120dpi
mdpi (medium) ~160dpi
hdpi (high) ~240dpi
xhdpi (extra-high) ~320dpi
xxhdpi (extra-extra-high) ~480dpi
xxxhdpi (extra-extra-extra-high) ~640dpi
通用的尺寸和密度按照基线配置(即normal尺寸和 mdpi密度)排列。 此基线基于第一代 Android 设备 (T-Mobile G1) 的屏幕配置,该设备采用 HVGA 屏幕(在 Android 1.6 之前,这是 Android 支持的唯一屏幕配置)。
每种通用的尺寸和密度都涵盖一个实际屏幕尺寸和密度范围。例如, 两部都报告normal屏幕尺寸的设备在手动测量时,实际屏幕尺寸和高宽比可能略有不同。类似地,对于两台报告 hdpi 屏幕密度的设备,其实际像素密度可能略有不同。 Android 将这些差异抽象概括到应用,使您可以只提供为通用尺寸和密度设计的UI,让系统按需要做最终调整(具体怎么调整下面第3部分会具体说明)。 下图说明不同的尺寸和密度如何粗略归类为不同的尺寸 和密度组。
在为不同的屏幕尺寸设计UI时,您会发现每种设计都需要最小空间。因此,上述每种通用的屏幕尺寸都关联了系统定义的最低分辨率。这些最小尺寸以“dp”单位表示 — 在定义布局时应使用相同的单位 — 这样系统无需担心屏幕密度的变化。
xlarge screens are at least 960dp x 720dp
large screens are at least 640dp x 480dp
normal screens are at least 470dp x 320dp
small screens are at least 426dp x 320dp
要针对不同的屏幕尺寸和密度优化应用的 UI,可为任何通用的尺寸和密度提供备用资源。 通常,应为不同的屏幕尺寸提供备选布局,为不同的屏幕密度提供备选位图图像。 在运行时,系统会根据当前设备屏幕的通用尺寸或密度匹配适当的资源。
2.3 Density independence
应用显示在密度不同的屏幕上时,如果保持用户界面元素的物理尺寸(从用户的视角)一致,也就实现了“Density independence” 。
保持Density independence很重要,因为如果没有此功能,UI 元素(例如 按钮)在低密度屏幕上看起来较大,在高密度屏幕上看起来较小。这些密度相关的大小变化可能给应用布局和易用性带来问题。下面两张图依次显示了应用不提供Density independence和提供Density independence时的差异。
在图 2 中,文本视图和位图可绘制对象具有以像素(px单位)指定的尺寸,因此视图的物理尺寸在低密度屏幕上更大,在高密度屏幕上更小。这是因为,虽然实际屏幕尺寸可能相同,但高密度屏幕的每英寸像素更多(同样多的像素在一个更小的区域内)。在图 3 中,布局尺寸以密度独立的像素(dp 单位)指定。由于密度独立像素的基线是medium密度屏幕,因此具有medium密度屏幕的设备看起来与图 2 一样,对于low密度(1dp = 0.75px)和high密度(1dp = 1.5px)的屏幕,文本视图和位图可绘制对象的大小与medium密度屏幕相同。
大多数情况下,确保应用中的density independence很简单,只需以适当的密度独立像素(dp 单位)或 "wrap_content" 指定所有布局尺寸值。
3. 基于屏幕密度的图片缩放规则
3.1 匹配规则
假设当前屏幕密度对应的通用分类为xhdpi,系统会优先在drawable-xhdpi目录中寻找需要的drawable资源,如果没有找到,就会依次在drawable-xxhdpi、drawable-xxxhdpi目录中寻找需要的drawable资源,如果还是没有找到,就会到drawable-nodpi中寻找需要的drawable资源,如果还是没有找到,就会依次在drawable-hdpi、drawable-mdpi、drawable、drawable-ldpi目录中寻找需要的drawable资源,如果还是没有找到,那是不可能的,因为这样的话编译都不会过的。
3.2 缩放规则
1> 按照上面的方式找到匹配的drawable资源后,假设当前屏幕密度为x,匹配的drawable资源所在的目录的屏幕密度为y,那就会按照 x/y 比例缩放匹配的drawable资源,以匹配当前的屏幕密度。
2> 对于放在有nodpi配置限定符的drawable资源目录中。例如:res/drawable-nodpi/icon.png,当系统使用此文件夹中的 icon.png 位图时,不会根据当前屏幕密度缩放。
3.3 举例验证:
1> 通过断点的方式获取到手机屏幕密度,如下图所示:
我的手机的屏幕密度为420dpi,对应屏幕密度通用分类中的xxhdpi(480dpi)。
2> 验证3.1 匹配规则
通过在drawable、drawable-ldpi、drawable-mdpi、drawable-hdpi、drawable-xhdpi、drawable-xxhdpi、drawable-xxxhdpi和drawable-nodpi目录中各放一张名称相同内容不同的图片,可以很容易验证3.1 中的规则,有兴趣的同学可以自己验证一下。
3> 验证3.2 缩放规则
仅在drawable-xxhdpi目录下放置一张图片,图片大小如下图所示:
在不进行缩放的情况下加载到内存中,内存中该图片的大小应该是:
507*760*4 = 1541280 byte
接着将这个图片放到imageview中,通过断点的方式看一下该图片被加载到内存中的实际大小:
可以看到图片的大小被压缩了,根据3.2中的规则计算一下缩小的过程:
缩放后的宽为:(420/480)*507 = 444
缩放后的高为:(420/480)*760 = 665
缩放后的大小为:444*665*4 = 1181040 byte,和上面截图中的大小相同。
下面仅在drawable-hdpi目录下放置一张图片,图片大小如下图所示:
在不进行缩放的情况下加载到内存中,内存中该图片的大小应该是:
700*1049*4 = 2937200 byte
接着将这个图片放到imageview中,通过断点的方式看一下该图片被加载到内存中的实际大小:
可以看到图片的大小被放大了,根据3.2中的规则计算一下放大的过程:
放大后的宽为:(420/240)*700 = 1225
放大后的高为:(420/240)*1049 = 1836
放大后的大小为:1225*1836*4 = 8996400 byte,和上面截图中的大小相同。
下面仅在drawable目录下放置一张图片,图片大小如下图所示:
在不进行缩放的情况下加载到内存中,内存中该图片的大小应该是:
800*577*4 = 1846400 byte
接着将这个图片放到imageview中,通过断点的方式看一下该图片被加载到内存中的实际大小:
由于系统假设默认资源( 没有配置限定符目录中的资源)针对基线屏幕密度 (mdpi) 而设计,因此drawable目录对应的屏幕密度是160dpi。
可以看到图片的大小被放大了,根据3.2中的规则计算一下放大的过程:
放大后的宽为:(420/160)*800 = 2100
放大后的高为:(420/160)*577 = 1515
放大后的大小为:2100* 1515*4 = 12726000 byte,和上面截图中的大小相同。
下面仅在drawable-nodpi目录下放置一张图片,图片大小如下图所示:
在不进行缩放的情况下加载到内存中,内存中该图片的大小应该是:
600*900*4 = 2160000 byte
接着将这个图片放到imageview中,通过断点的方式看一下该图片被加载到内存中的实际大小:
可以看到该图片的大小没有发生改变。