屏幕适配
名词解释
先对Android屏幕适配相关的名词作解释,最后会通过Android系统的源码,进行进一步的理解。毕竟源码是最好的文档。
px 像素
屏幕的实际物理像素,我们小时候家里的大屁股电视,离近了看,可以看到一个一个的小点,那就是像素点。像素是一切其他单位的基础。我们可以理解其他单位为逻辑单位。这里逻辑单位都可以根据自己的特性,通过一个公式转换成物理像素。<font color='red'>所有的View在最终绘制的时候都会以像素为单位</font>。逻辑单位的存在,是为了让开发者在不同情况下,使用不同的逻辑单位,以达到屏幕适配的目的。所以,<font color='red'>明白各种逻辑单位到物理像素的转换过程对于我们更好的做屏幕适配至关重要</font>。
举个?:三星Note4 的屏幕分辨率是 2560x1440,这就代表了他的纵向有2560个像素点,横向有1440个像素点。
屏幕像素密度
屏幕像素密度的单位是ppi(pix per inch),即每英寸屏幕所拥有的像素数,像素密度越大,显示画面细节就越丰富。
举个?:三星Note4 的参数说明中可以看到他的屏幕像素密度是515ppi.
这个数据可以通过如下公式计算出来:
纵向分辨率的平方+横向分辨率的平方然后再开根号即为屏幕对角线的像素数,然后再除以该手机的屏幕尺寸5.7,最后 x 的值是 515.29992282...,基本上就是515.
density
density 表示的是屏幕密度,其单位是dpi(dot per inch).那么我们怎么得到这个属性呢?是通过计算得出来的吗? 其实这个属性是Android系统的的一个配置属性。是设备制造商在配置在Android系统里的属性。我们可以从Android文件系统/system/build.prop文件找到这个属性:ro.sf.lcd_density。 我们可以通过以下命令来获取到:
adb shell cat /system/build.prop | grep density
如下图
dpi(dots per inch) 每英寸点数 百科,这是一个逻辑单位。通常屏幕大时,density就大,屏幕小时,density就小。
举个?:拿三星Note4 来举例子,上图就是我从note上获取的值。可以看到,他的density是640dpi,即每英寸640个点。
dip(density independent pixels) 简称dp,设备无关像素
这是我们在写xml布局的时候最常用到的单位。因其与设备无关,能帮我们更好的做屏幕适配。因现在市面各种手机虽然分辨千变万化,但大部分手机屏幕的dp值为640*360。产生这中现象主要是因为各大手机厂商也要考虑自己的出的手机能不能和现有的应用适配。若自己生产的手机的屏幕dp比较奇葩,那么在安装市面的应用的时候就会因屏幕适配问题显得乱七八槽,而不懂技术的普通用户就会以为这手机真是太垃圾了。当然,若手机厂商能让所有的应用开发商为自己奇葩的屏幕dp做适配,就另当别论了,不过好像世界还没有哪个手机厂商有这样的号召力。
sp(scale independent pixels) 可缩放设备无关像素
这个单位是专门来标注文字的。它其实是在dp的基础上有乘了一个系数。那有人说了,为什么要乘以一个系数呢?这是因为人的习惯不同,有的人喜欢大字体,有的人喜欢小字体。我们的应用为了要满足不同用户的习惯,就要能让用户根据自己的喜好进行设置。 那这个是用户在什么地方设置的呢? 在5.0系统下可通过如下方式设置: “设置” -> "显示" -> "字体大小"
在我的三星note4:“设置“ -> "辅助功能" -> "视觉" -> "字体大小"
pt
point是一个标准的长度单位,1pt=1/72英寸,用于印刷业,非常简单易用
mm
毫米,长度单位。1英寸(in)=25.4毫米(mm)。
in
英寸,长度单位。
两个关于关于各个单位转换成像素的类
DisplayMetrics
DisplayMetrics这个类封装屏幕的所有信息,单位转换的时候会用到。下面是对这个类中一些重要的变量和方法的解释。 DisplayMetrics 对象的获取方式:DisplayMetrics metrics = Context.getResources().getDisplayMetrics();
//DisplayMetrics.class
/**
* A structure describing general information about a display, such as its
* size, density, and font scaling.
* <p>To access the DisplayMetrics members, initialize an object like this:</p>
* <pre> DisplayMetrics metrics = new DisplayMetrics();
* getWindowManager().getDefaultDisplay().getMetrics(metrics);</pre>
*
* 一个描述屏幕信息的类,例如屏幕尺寸,屏幕密度,字体缩放比例等。
* 可以通过如下方式来获取它:
* DisplayMetrics metrics = Context.getResources().getDisplayMetrics();
*
*/
......
/**
* The absolute width of the available display size in pixels.
* 屏幕横向绝对像素数,对于note4来说,就是1440
*/
public int widthPixels;
/**
* The absolute height of the available display size in pixels.
* 屏幕纵向绝对像素数,对于note4来说,就是2560
*/
public int heightPixels;
/**
* The logical density of the display. This is a scaling factor for the
* Density Independent Pixel unit, where one DIP is one pixel on an
* approximately 160 dpi screen (for example a 240x320, 1.5"x2" screen),
* providing the baseline of the system's display. Thus on a 160dpi screen
* this density value will be 1; on a 120 dpi screen it would be .75; etc.
*
* <p>This value does not exactly follow the real screen size (as given by
* {@link #xdpi} and {@link #ydpi}, but rather is used to scale the size of
* the overall UI in steps based on gross changes in the display dpi. For
* example, a 240x320 screen will have a density of 1 even if its width is
* 1.8", 1.3", etc. However, if the screen resolution is increased to
* 320x480 but the screen size remained 1.5"x2" then the density would be
* increased (probably to 1.5).
*
* @see #DENSITY_DEFAULT
*
* 屏幕的逻辑密度。它是dip(device independent pixel)转像素的缩放因数.
*
* 屏幕密度为160dpi的屏幕上,1dpi等于1像素(例如 240x320, 1.5寸x2寸的屏幕)。Android
* 系统以这个数据作为基准,定义了整个Android的显示系统。
* 因此在160dpi的屏幕上,下面density 取值为1;在120dpi的屏幕上,下面density的取值为0.75.
*
* 这个值并不严格和屏幕的真实尺寸相关(与下面提到的 xdpi,ydpi 不同)。
* 因此,在160dpi屏幕上,下面density的取值为1;
*
* 例如,一个 240x320 的屏幕,density 的值为1,无论这个屏幕的宽度是1.8寸,1.3寸,或者
* 其他值。然而,如果屏幕的分辨率增加到 320x480,但屏幕的尺寸仍然是1.5寸x2寸,那么 density
* 的值可能会增加(可能会是1.5)。
*
* density的值为: densityDpi/160,下面我会通过源码来了解这个值究竟是怎么的出来的。
* 这个值的本质就是1dp对应几个像素
* 例如在note4手机上,这个值为640/160=4。也就是说1dp=4px。
*/
public float density;
/**
* The screen density expressed as dots-per-inch. May be either
* {@link #DENSITY_LOW}, {@link #DENSITY_MEDIUM}, or {@link #DENSITY_HIGH}.
*
* 以 dots-per-inch 为单位,屏幕的密度。取值为DENSITY_LOW,DENSITY_MEDIUM,DENSITY_HIGH
* 等这几个屏幕密度之一。
* 这个变量就对应我们上文提到的 density.
* 例如在note4中就是640,对应 DENSITY_XXXHIGH
*
*/
public int densityDpi;
/**
* A scaling factor for fonts displayed on the display. This is the same
* as {@link #density}, except that it may be adjusted in smaller
* increments at runtime based on a user preference for the font size.
*
* 用于文字显示的缩放因子。
* 它和density类似,但它会根据用户字体大小的偏好设置而改变。
*/
public float scaledDensity;
/**
* The exact physical pixels per inch of the screen in the X dimension.
* 屏幕横向的每英寸精确像物理素数
*/
public float xdpi;
/**
* The exact physical pixels per inch of the screen in the Y dimension.
* 屏幕纵向的每英寸精确物理像素数
*/
public float ydpi;
...
/**
* 这个方法是获取系统参数屏幕密度用的
*/
private static int getDeviceDensity() {
// qemu.sf.lcd_density can be used to override ro.sf.lcd_density
// when running in the emulator, allowing for dynamic configurations.
// The reason for this is that ro.sf.lcd_density is write-once and is
// set by the init process when it parses build.prop before anything else.
return SystemProperties.getInt("qemu.sf.lcd_density",
SystemProperties.getInt("ro.sf.lcd_density", DENSITY_DEFAULT));
}
举个?:拿三星Note4 来举例子,在手机上执行如下代码:
DisplayMetrics metrics = getResources().getDisplayMetrics();
LogUtil.i(metrics.toString());
LogUtil.i("metrics.densityDpi:" + metrics.densityDpi);
会打印出如下结果
DisplayMetrics{density=4.0, width=1440, height=2560, scaledDensity=4.0, xdpi=508.0, ydpi=516.063}
metrics.densityDpi:640
TypedValue
TypedValue这个类主要用来处理单位转换的,他将一切的逻辑单位转换为像素单位
// TypedValue.class
//这个方法是将各种不同的单位转换成对应的像素
public static float applyDimension(int unit, float value,
DisplayMetrics metrics)
{
switch (unit) {
case COMPLEX_UNIT_PX://当单位是像素时,直接将value返回
return value;
case COMPLEX_UNIT_DIP://当单位是dip时,value * metrics.density
return value * metrics.density;
case COMPLEX_UNIT_SP//当单位是sp时,value * metrics.scaledDensity
return value * metrics.scaledDensity;
case COMPLEX_UNIT_PT://当单位是pt的时候,为 value * 每英寸像素数 / 72,因为上文已经说过了,1in=72pt
return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN://当单位为英寸的时候,为 value * 每英寸像素数
return value * metrics.xdpi;
case COMPLEX_UNIT_MM://当单位为mm的时候,为 value * 每英寸像素数/24.5,因为 1英寸(in)=25.4毫米(mm)
return value * metrics.xdpi * (1.0f/25.4f);
}
return 0;
}
单位转换举例
我们现在用Note4这款手机来举例子
- 我们先设置系统字体为"大"
- 执行如下代码获取屏幕信息
DisplayMetrics metrics = getResources().getDisplayMetrics();
LogUtil.i(metrics.toString());
LogUtil.i("metrics.densityDpi:" + metrics.densityDpi);
会打印出如下结果
DisplayMetrics{density=4.0, width=1440, height=2560, scaledDensity=4.76, xdpi=508.0, ydpi=516.063}
metrics.densityDpi:640
- 我们得到了density,scaledDensity,xdpi等值,带入上面公式就可以计算出对应的像素了
来点源码
我们已经知道了各个单位的意思,转换成像素的方式,爱钻研的我们怎么能满足于文档上介绍的呢,我们一定要在代码上实实在在的看到,才能确信。下面就从源码的层面上,来看下density,scaledDensity这些值是如何得到的。
1. density
2. scaledDensity
3. xdpi
- dp转px
dp转换成px的公式如下:px=dp*(density/160)。 由公式我们可以看到,像素的转换只和手机的像素密度有关。 举个?:拿三星Note4 来举例子。note4的屏幕是640,也就是一个dp等于四个像素点。 我们把公式变一下:dp=px/(density/160) 有这个公式,我们就可以计算出来一个手机横向和纵向究竟有多少个dp。 举个?:拿三星Note4 来举例子。note4的屏幕横向有1440个像素,可以得出这款手机的横向一共有360个dp。同样纵向有640个dp。
热门机型
no. | 手机型号 | 分辨率 | 物理尺寸 | PPI | dpi | dp |
---|---|---|---|---|---|---|
1 | oppo r9s/OPPO R9(电信版) | 1920*1080 | 5.5 | 401 | 480 | 640*360 |
2 | vivo x7 | 1920*x1080 | 5.2 | 424 | 480 | 643*360 |
3 | vivo x9 | 1920*x1080 | 5.5 | 401 | 480 | 640*360 |
4 | 华为Mate 8 | 1920*1080 | 5.2 | 424 | 480 | 640*360 |
5 | 华为畅享5S | 1280*720 | 5 | 294 | 320 | 640*360 |
6 | 红米Note1s | 1280*720 | 4.7 | 312 | 320 | 640*360 |
7 | vivo Y51A | 960*540 | 5 | 220 | 240 | 640*360 |
8 | 酷派Y1 | 960*540 | 5.5 | 200 | 240 | 640*360 |
9 | vivo Xplay3S | 2560*1440 | 6 | 490 | 640 | 640*360 |
10 | GALAXY Note 4 | 2560*1440 | 5.2 | 515 | 640 | 640*360 |
11 | GALAXY s6 | 2560*1440 | 5.1 | 576 | 640 | 640*360 |
12 | nubia z5 Mini | 1920*1080 | 4.7 | 312 | 480 | 640*360 |
13 | nubia z9 max | 2560*1440 | 5.2 | 515 | ||
14 | 海信x1 | 1920*1080 | 6.8 | 324 | ||
15 | 魅族 mx4 | 1920x1152 | 5.36 | 418 | ||
16 | vivo y11 | 800*480 | 4 | 233 | 240 | 533.333*320 |
六种通用的密度
- ldpi(低)~120dpi
- mdpi(中)~160dpi
- hdpi(高)~240dpi
- xhdpi(超高)~320dpi
- xxhdpi(超超高)~480dpi
- xxxhdpi(超超超高)~640dpi
屏幕适配情景
- 根据宽度等比例缩放高度(通过代码来实现)
- 等分屏幕的宽度或者
- 加载大图是否比现在性能有损耗
- 大图加载到内存中是否占用了更大的内存
- 写布局文件的时候要尽量用dimens
- 特殊情况可想设计人员给一个比例,通过计算来动态确定大小
和IOS切图的对应关系
许多公司因为各种原因,设计师只会按照IOS出一套设计图和切图。这时候,我们需要知道如何来复用IOS的切图。下表是ISO切图和Android不同分辨率对照关系表:
切图 | 文件夹 | dp和px的对应关系 | pt和px的对应关系 | 对应ios手机 | 切图后缀 |
---|---|---|---|---|---|
0.75x | ldpi(low)-120dpi | 1dp=0.75px | |||
1x | mdpi(medium)-160dpi | 1dp=1px | 1pt-1px | 初代iPhone | @1 x |
1.5x | hdpi(high)-240dpi | 1dp=1.5px | |||
2x | xhdpi(extra-high)-320dpi | 1dp=2px | 1pt-2px | iPhone4 326ppi | @2x |
iPhone5/5s 326ppi | @2x | ||||
iPhone5/5s 326ppi | @2x | ||||
3x | xxhdpi(extra-extra-high)-640dpi | 1dp=3px | 1pt=3px | iPhone6 plus 401ppi | @3x |
4x | xxxhdpi(extra-extra-extra-high)-640dpi | 1dp=4px | 1pt=4px |
所以对于ios的@3x的图片,应该放在xxhdpi文件夹下。@2x的图片,应该放在xhdpi下面。
Q&A
待处理问题
[x]----- sp转px
[x]----- 拿一个手机做例子,分析其各个数据
[x]----- 通过代码来分析各个单位
[x]----- 为什么要有dpi这个概念
[x]----- Android系统是根据手机的density来确定到底使用哪个文件夹下的图片,代码分析。
[x]----- 图片的缩放过程.
相关资源
- Samsung emulator skins
- Fit More Content on Your Screen by Changing the Pixel Density on Your Android Device
- Density-independent Pixels
- What does DisplayMetrics.scaledDensity actually return in Android?