屏幕适配基础
基础概念
dpi(ppi)、dip(dp)、px、sp、density、分辨率、屏幕尺寸概念
1、dpi 概念:dots per inch , 直接来说就是一英寸多少个像素点。常见取值 120(px/inch),160 (px/inch) ,240 (px/inch) 。我一般称作像素密度,简称密度。单位是:像素/英寸 (px/inch) 。ppi 和 dpi 其实原理是相同的,都是每英寸上的点数,对于手机屏幕来说,这两个概念已经是同义词了。
由屏幕对角线的像素数和屏幕尺寸的比例
dpi = (√(screenHeight^2 + screenWidth^2)) / 屏幕大小 (inch)
2、dip 概念:device independent pixels ,设备无关像素,dp 就是 dip。是像素和密度的比
dp = px / density
3、px 概念:像素 单位:pxpx = dp*density
4、sp:scaled pixels(放大像素). 主要用于字体显示 best for textsize。根据 google 的建议,TextView 的字号最好使用 sp 做单位,而且查看 TextView 的源码可知 Android 默认使用 sp 作为字号单位。
5、density 概念:直接翻译的话貌似叫密度。常见取值 1.5 , 1.0 。没有单位。
密度是 dpi 和 160 的比例
density = dpi / 160
6、分辨率概念:横纵 2 个方向的像素点的数量,常见取值 480x800(px),320x480(px) 单位:px
7、屏幕尺寸:屏幕对角线的长度。这里还涉及另外一个问题,就是屏幕比例的问题。因为只确定了对角线长,2 边长度还不一定。所以有了 4:3、16:9 这种,这样就可以算出屏幕边长了。
英寸是 dpi 或 ppi 的比例
inch = px / dpi = px / ppi
屏幕的分辨率和屏幕的尺寸是固定的,所以其 dpi 像素密度也是固定的,density 也是固定的
dpi 和 ppi
ppi 是物理设备固定不变的,可能多个 ppi 设备会对应同一种 dpi
dpi 是软件层面上的,可能会变更,dpi 可能和 ppi 值不一样。
常见的 dpi
1
2
3
4
5
6
7
8
9
10
11
12
ldpi:低密度屏幕;约为 120dpi。
mdpi:中等密度(传统 HVGA)屏幕;约为 160dpi。
hdpi:高密度屏幕;约为 240dpi。
xhdpi:超高密度屏幕;约为 320dpi。此项为 API 级别 8 中的新增配置
xxhdpi:绝高密度屏幕;约为 480dpi。此项为 API 级别 16 中的新增配置
xxxhdpi:极高密度屏幕使用(仅限启动器图标,请参阅支持多种屏幕中的注释);约为 640dpi。此项为 API 级别 18 中的新增配置
nodpi:可用于您不希望为匹配设备密度而进行缩放的位图资源。
tvdpi:密度介于 mdpi 和 hdpi 之间的屏幕;约为 213dpi。此限定符并非指“基本”密度的屏幕。它主要用于电视,且大多数应用都不使用该密度 — 大多数应用只会使用 mdpi 和 hdpi 资源,而且系统将根据需要对这些资源进行缩放。此项为 API 级别 13 中的新增配置
anydpi:此限定符适合所有屏幕密度,其优先级高于其他限定符。这非常适用于矢量可绘制对象。此项为 API 级别 21 中的新增配置
nnndpi:用于表示非标准密度,其中 nnn 是正整数屏幕密度。此限定符不适用于大多数情况。使用标准密度存储分区,可显著减少因支持市场上各种设备屏幕密度而产生的开销。
六个基本密度之间的缩放比为 3:4:6:8:12:16(忽略 tvdpi 密度)。因此,9x9 (ldpi) 位图相当于 12x12 (mdpi)、18x18 (hdpi)、24x24 (xhdpi) 位图,依此类推。
如果您认为图像资源在电视或其他某些设备上的呈现效果不够好,进而想尝试使用 tvdpi 资源,则缩放系数应为 1.33*mdpi。例如,mdpi 屏幕的 100px x 100px 图像应相当于 tvdpi 屏幕的 133px x 133px 图像。
dp 和 sp 区别
dp 一般用于控件的宽高
sp 一般用于字体大小,随着系统字体大小切换而改变,如果不想字体随着系统字体切换而改变,用 dp
px = dp*density
density 是 DisplayMetrics 中的成员变量,而 DisplayMetrics 实例通过 Resources#getDisplayMetrics 可以获得,而 Resources 通过 Activity 或者 Application 的 Context 获得。
DisplayMetrics 中和适配相关的几个变量:
- DisplayMetrics#density 就是上述的 density
- DisplayMetrics#densityDpi 就是上述的 dpi
- DisplayMetrics#scaledDensity 字体的缩放因子,正常情况下和 density 相等,但是调节系统字体大小后会改变这个值
他们最终都是调用 TypedValue#applyDimension(int unit, float value, DisplayMetrics metrics) 来进行转换:
Android 中 drawable 适配 (Android 图片寻找规则)
DPI 的计算
DPI(dots per inch),表一英寸多少个像素点,称作像素密度。
DPI = √(屏幕高^2+屏幕宽^2) / 屏幕对角线 (px/inch)
Aandroid 的 drawable 文件一共可以有:
- drawable-ldpi(低密度)
- drawable-mdpi(中等密度)
- drawable-hdpi(高密度)
- drawable-xhdpi(超高密度)
- drawable-xxhdpi(超超高密度)
- drawable-xxxhdpi(超超超高密度)
- drawable-nohdpi(无缩放)
- 当然还加上默认的 drawable
密度 | dpi 范围 | 分辨率 |
---|---|---|
ldpi | 0dpi~120dpi | |
mdpi | 120dpi~160dpi | |
hdpi | 160dpi~240dpi | |
xhdpi | 240dpi~320dpi | |
xxhdpi | 320dpi~480dpi | |
xxxhdpi | 480dpi~640dpi |
获取设备的像素,density,densityDpi 等
1
2
3
4
5
6
int widthPixels = getResources().getDisplayMetrics().widthPixels; // 屏幕宽
int heightPixels = getResources().getDisplayMetrics().heightPixels; // 屏幕高
float density = getResources().getDisplayMetrics().density; // density
int densityDpi = getResources().getDisplayMetrics().densityDpi; // 设备DPI
float xdpi = getResources().getDisplayMetrics().xdpi; // x方向dpi
float ydpi = getResources().getDisplayMetrics().ydpi; // y方向dpi
Android 系统对图片的缩放和放大
每一种密度的 dpi 范围都有一个最大值,这个最大值之间的比例就是图片会被系统自动放大的比例。
实际图片展示内存占用计算,默认 Bitmap 是 ARGB_8888,占用 4B 字节空间:
1
2
实际展示尺寸 = 原图尺寸*(设备dpi/文件夹dpi)
占用内存 = 实际尺寸长 * 实际尺寸宽 * 位深度
- 在高 dpi 的设备,如果图片放在低 dpi 的文件的话,系统认为这是为低密度设备设计的,那么图片会被放大,占用内存增大
- 在低 dpi 的设备,如果图片放在高 dpi 的文件的话,系统认为这是为高密度设备设计的,那么图片会被缩小
Android 图片缩放查找规则
当我们使用资源 id 去引用一张图片时,Android 会使用一些规则来去帮我们匹配最合适的图片。什么叫最合适,比如我的手机屏幕密度为 xxhdpi,那么 drawable-xxhdpi 文件夹下的图片就是最合适的图片。因为,引用 android_logo.png 这张图片时,如果 drawable-xxhdpi 文件夹下有这张图片就会优先被使用,在这种情况下,图片是不会被缩放的。但是,如果 xxhdpi 文件夹下没有这张图片时,系统就会自动去其他文件夹下找这张图了,优先会去更高密度的文件夹下去寻找,我们的场景就是 drawable-xxxhdpi 文件夹,然后发现这个没有这张图,接下来去尝试再找更高密度的文件夹,发现没有更高密度的了,这个时候会去 drawable-nodpi
文件夹下找这张图,发现也没有,那么就会去更低密度的文件夹下面找,依次是:drawable-xhdpi → drawable-hdpi → drawable-mdpi → drawable-ldpi。
如果在 drawable-ldpi 文件夹下找到这张图,但是系统会认为这张图是专门为低密度的设备所设计的,如果直接将这张图在当前高密度设备上使用就会有可能出现像素过低的情况,于是系统自动帮我们做了这样一个放大的操作。
同样,如果系统在 drawable-xxxhdpi 文件夹下找到这张图,它会认为这张图是为高密度的设备所设计的,如果直接将这张图在当前设备上使用就有可能出现像素过高的情况,于是会自动帮我们做了一个缩小的操作。
drawable-nodpi 这个文件夹,是一个密度无关的文件夹,放在这里的图片系统不会对它进行自动缩放,原图片是多大就会实际展示多大。但是要注意一个加载顺序,drawable-nodpi 文件夹是在匹配密度文件夹和更高密度文件夹都找不到的情况下才会去这里查找图片,因为放在 drawable-nodpi 文件夹的图片通常不建议再放别的文件夹下。
匹配规则总结
- 如果图片所在目录 dpi 低于匹配目录,那么该图片被认为是为低密度设备需要的,现在要显示在高密度设备上,图片会被放大。
- 如果图片所在目录 dpi 高于匹配目录,那么该图片被认为是为高密度设备需要的,现在要显示在低密度设备上,图片会被缩小。
- 如果图片所在目录为 drawable-nodpi,则无论设备 dpi 为多少,保留原图片大小,不进行缩放。
Android 图片适配
将一张图片放在低密度的文件下,那么在高密度设备上显示图片会被自动放大;将图片放在高密度文件夹下,低密度设备上显示图片时会被自动缩小。
一张图片被缩小了之后显示并没有什么副作用,但是一张图片被放大了之后就意味着要占用更多的内存,因为图片放大了,像素点也就变得多了,而每个像素点都是要占用内存的。
在 drawable-hdpi 下的一张图片,程序会出现 OOM,但是放到 drawable-xxhdpi 或者更高,就不会出现 OOM。
所以:图片资源应该尽量放在高密度文件夹下,这样可以节省图片占用的内存;UI 设计图片也尽量面向高密度的设备来进行设计。
案例
一张 246x246 的 png 图片,放在三台不同的手机:
- 手机 1(Sony Xperia S):480x800, density:0.75, densityDpi:120, 属于 ldpi 设备
- 手机 2(Nexus 7):800x1216, density:1.3312501, densityDpi:213, 属于 hdpi 设备
- 手机 3(Nexus 6):1440x2392, density:3.5, densityDpi:560, 属于 xxxhdpi 设备
图片放 ldpi(120dpi)
计算占用内存:
1
2
3
实际长: = 246x(120/120) = 246
实际宽: = 246x(120/120) = 246 (没有缩放放大)
占用内存: 246x246x4B = 242064B
- 手机 2(800x1216) 表现 –hdpi 设备
bitmap byteCount: 763876 = 746KB - 手机 3(1440x2392) 表现 –xxxhdpi 设备
bitmap byteCount: 5271616 = 5.03MB
计算占用内存:
1
2
3
实际长: = 246x(560/120) = 1148
实际宽: = 246x(560/120) = 1148 (放大了4.67倍)
占用内存: 1148x1148x4B = 5271616B
图片放 hdpi(240dpi)
图片放 xxxhdpi(640dpi)
- 手机 1 –ldpi 设备
bitmap byteCount: 8464 = 8.27KB - 手机 2 –hdpi 设备
bitmap byteCount: 26896 = 26.27KB - 手机 3 –xxxhdpi 设备
bitmap byteCount: 184900 = 181KB
计算占用内存:
实际长: = 246x(560/640) = 215.25
实际宽: = 246x(560/640) = 215.25 (缩放了 0.875 倍)
占用内存: 215.25x215.25x4B = 185330.25B
Ref
- Android drawable 微技巧,你所不知道的 drawable 那些细节 (guo_lin)