玩转Android drawable图片适配
众所周知,Android机型众多,屏幕尺寸、分辨率各有不同。对于Android开发人员来说,如何提高APP中图片对各种机型的适配是基本技能之一。借着项目中遇到的图片适配问题,在总结项目时,就想着顺带把这部分好好捋一捋,作为一个记录,也为不是很清楚这部分的人提供一个参考。
先说mipmap
采用Android Studio开发Android APP,在项目的res目录下,会多出几个以mipmap开头的文件夹。
根据Android官方的描述,mipmap仅仅用于存放APP启动图标,可由Image Asset Studio生成。Image Asset Studio会生成mdpi、hdpi、xhdpi、xxhdpi、xxxhdpi五种尺寸的图标。图标最好不要随意定义尺寸,分辨率过低会模糊,过高徒增APK包大小。各种密度下的图标建议尺寸为
密度 | 建议尺寸 |
---|---|
mdpi | 48*48 |
hdpi | 72*72 |
xhdpi | 96*96 |
xxhdpi | 144*144 |
xxxhdpi | 192*192 |
如果要上传到Google Play,还需要一张512*512的图片用于Google Play Store。
再说drawable图片适配
Android系统可以在具有不同屏幕尺寸和密度的设备上运行,并将每个应用的用户界面调整为适应其显示的屏幕,会进行缩放和大小调整。为了最大程序优化更多设备上的用户体验,开发者需要针对不同的屏幕尺寸和密度优化应用。对于Android智能手机来说,屏幕大小、分辨率、密度均不尽相同,那么图片适配就成了Android中优化应用必不可少的环节之一。
相关概念
-
dpi
每英寸点数,全称dots per inch。用来表示屏幕密度,即屏幕物理区域中的像素量。高密度屏幕比低密度屏幕在给定物理区域的像素要多。 -
dp
即dip,全称device independent pixel。设备独立像素,是一种虚拟像素单位,用于以密度无关方式表示布局维度或位置,以确保在不同密度的屏幕上正常显示UI。在160dpi的设备上,1dp=1px。 -
density
设备的逻辑密度,是dip的缩放因子。以160dpi的屏幕为基线,density=dpi/160。getResources().getDisplayMetrics().density
-
sp
缩放独立像素,全称scale independent pixel。类似于dp,一般用于设置字体大小,可以根据用户设置的字体大小偏好来缩放。
六种通用密度
Android系统为了简化开发者为多种屏幕设计用户界面的方式,Android将实际屏幕尺寸和范围作了通用规定,称作“根据可用屏幕宽度管理屏幕尺寸的新技术”。六种通用密度为
密度 | dpi范围 |
---|---|
ldpi(低) | ~120dpi |
mdpi(中) | ~160dpi |
hdpi(高) | ~240dpi |
xhdpi(超高) | ~320dpi |
xxhdpi(超超高) | ~480dpi |
xxxhdpi(超超超高) | ~640dpi |
通用密度是以mdpi(中)为基线配置的,此基线基于第一代Android设备(T-Mobile G1)的屏幕配置。
Android系统适配原则
Android为了更好地优化应用在不同屏幕密度下的用户体验,在项目的res目录下可以创建drawab-[density](density为6种通用密度名)目录,开发者在进行APP开发时,针对不同的屏幕密度,将图片放置于对应的drawable-[density]目录,Android系统会依据特定的原则来查找各drawable目录下的图片。查找流程为:
1. 先查找和屏幕密度最匹配的文件夹。如当前设备屏幕密度dpi为160,则会优先查找drawable-mdpi目录;如果设备屏幕密度dpi为420,则会优先查找drawable-xxhdpi目录。
2. 如果在最匹配的目录没有找到对应图片,就会向更高密度的目录查找,直到没有更高密度的目录。例如,在最匹配的目录drawable-mdpi中没有查找到,就会查找drawable-hdpi目录,如果还没有查找到,就会查找drawable-xhdpi目录,直到没有更高密度的drawable-[density]目录。
3. 如果一直往高密度目录均没有查找,Android就会查找drawable-nodpi目录。drawable-nodpi目录中的资源适用于所有密度的设备,不管当前屏幕的密度如何,系统都不会缩放此目录中的资源。因此,对于永远不希望系统缩放的资源,最简单的方法就是放在此目录中;同时,放在该目录中的资源最好不要再放到其他drawable目录下了,避免得到非预期的效果。
4. 如果在drawable-nodpi目录也没有查找到,系统就会向比最匹配目录密度低的目录依次查找,直到没有更低密度的目录。例如,最匹配目录是xxhdpi,更高密度的目录和nodpi目录查找不到后,就会依次查找drawable-xhdp、drawable-hdpi、drawable-mdpi、drawable-ldpi。
举个例子,假如当前设备的dpi是320,系统会优先去drawable-xhdpi目录查找,如果找不到,会依次查找xxhdpi → xxxhdpi → hdpi → mdpi → ldpi。对于不存在的drawable-[density]目录直接跳过,中间任一目录查找到资源,则停止本次查找。
总结一下图片查找过程:优先匹配最适合的图片→查找密度高的目录(升序)→查找密度低的目录(降序)。
资源适配流程简单归纳如下
关于Android适配更详细的介绍可以参见Android 如何查找最佳匹配资源,当然你可能需要搭个梯子(不过,Google已经发布了针对中国的开发者网站,对应的Android中国开发者网站为:developer.android.google.cn,可以免梯子)。
图片的放大和缩小
前述说到Android为了能够更好地适配各种屏幕,会依据当前设备的dpi对drawable-[density]目录中的图片进行缩放,那么什么情况下图片被放大,什么情况下图片被缩小呢?
为了更好的描述,把“符合当前设备dpi的drawable目录”表示为”匹配目录“。比如,设备的dpi为320,这匹配目录为drawable-xhdpi;设备的dpi为150,则匹配目录为drawable-mdpi。图片的放大和缩小遵循以下规律:
- 如果图片所在目录为匹配目录,则图片会根据设备dpi做适当的缩放调整。
- 如果图片所在目录dpi低于匹配目录,那么该图片被认为是为低密度设备需要的,现在要显示在高密度设备上,图片会被放大。
- 如果图片所在目录dpi高于匹配目录,那么该图片被认为是为高密度设备需要的,现在要显示在低密度设备上,图片会被缩小。
- 如果图片所在目录为drawable-nodpi,则无论设备dpi为多少,保留原图片大小,不进行缩放。
那么六种通用密度下的缩放倍数是多少呢?以mdpi为基线,各密度目录下的放大倍数(即缩放因子density)如下
密度 | 放大倍数 |
---|---|
ldpi | 0.75 |
mdpi | 1.0 |
hdpi | 1.5 |
xhdpi | 2.0 |
xxhdpi | 3.0 |
xxxhdpi | 4.0 |
例如,当前设备的dpi是480(即xxhdpi),那么对于存放于mdpi目录中的图片会被放大三倍。对于很多设备,其dpi并不刚好是六种通用密度最大dpi,这种情况下,图片的缩放倍数如何计算呢?
稍微思考一下,我们就可以得到通用的缩放倍数(缩放因子)计算方法:对于任意设备,各drawable-[density]目录下的图片放大倍数的计算公式
那么,图片的实现显示尺寸通过图片尺寸乘以缩放倍数就可以得到了。
实例验证
-
验证图片的放大和缩小
在配置为1080×1920 - 420dpi的模拟器上,从网上找一张Android logo图片分别放在drawable-mdpi、drawable-xxhdpi、drawable-xxxhdpi文件夹下,查看图片的显示效果(如下)。从图中可以明显看到图片的放大和缩小,且比设备屏幕密度低的drawable-mdpi目录图片被放大,比设备屏幕密度高的drawable-xxxhdpi目录图片被缩小。
-
验证缩放倍数
在Sketch里简单绘制一张图,分别导出一倍图(1x)和三倍图(3x),并假设实际标注为一倍图的尺寸。表示如下
在配置为1080×1920 - 420dpi的模拟器上,1倍图和3倍图在wrap_content的情况下,宽高应该符合上表。按照上面对放大倍数的分析,分别把1倍图和3倍图置于drawable-mdpi和drawable-xxhdpi目录下,查看图片宽高。
该模拟器屏幕dpi横纵方向均为420,drawable-mdpi目录最大dpi值为160,drawable-xxhdpi目录最大dpi值为480。通过上述分析的计算公式,可计算图片预期宽高:
-
drawable-mdpi
scale = 420/160
1x:98 × scale = 98 × 420/160 = 257
3x:294 × scale = 294 × 420/160 = 772 -
drawable-xxhdpi
scale = 420/480
1x:98 × scale = 98 × 420/480 = 86
3x:294 × scale = 294 × 420/480 = 257
由于像素没有小数,上述计算结果进行了四舍五入。从结果可以看出,公式计算结果和实际显示效果一致。另一方面,在该dpi下,图片放置于drawable-xxhdpi目录下,图片的显示宽高更接近于图片实际大小。
这部分的验证大家可以自己测试一下,基本规律是没有问题的。
-
最后定切图
到此,我们已经知道了Android会按照特定规则对图片进行缩放,以更好地适配各种配置的屏幕。那么在我们关注切图之前,首先考虑一下图片缩放带来的影响。
嗯,比如容易想到的一点就是会引起内存的变化。当一张图片被放大时,像素增加,必然会引起内存占用量增加;图片被缩小时,像素减少,内存占用量就会降低。内存的使用量可通过Android Monitor来查看。对于上述Android logo那张图片,同样在420dpi的设备上,分别将图片放置于drawable-mdpi和drawable-xxhdpi目录下,内存占用情况如下
可以看到,仅仅一张图片的内存占用差别就已经在MB级别了。图片放大的内存成本将是不得不考虑的一个重要因素了。
关于切图的选取,Android官方给的建议,各种密度都给出一套图,分别放置在对应的drawable目录下,这种适配是最好的。但也存在问题,一是这种方式会增大安装包的大小;二是很多公司UI在出图时只会出一套。
在这种情况下,怎么使用好这一套切图呢?由于目前的Android智能手机的屏幕基本都在1080p了,屏幕的dpi多数都处于320~480,为了更好地适配,同时为了节省内存成本,建议将切图放置在drawable-xxhdpi目录,同时建议UI针对该密度的设备设计切图。如果UI的切图基于不同尺寸设计,Sketch导出切图时须调整相应的倍数。
例如,假设切图基于376×667的一倍屏幕设计,而要适配1080×1920的屏幕,导出三倍图存放于drawable-xxhdpi目录是适配最好的。
好了,关于drawable图片适配方面就暂时介绍到这,相信看了后会有一定的认识,在处理平时项目相关问题时,也会有一定的想法了。