Android 屏幕密度解惑

由于工作原因,发现很多 Android 初学者对屏幕密度等相关概念以及对应计算方式容易模糊,因此本文对相关知识进行简单介绍。文中只对概念进行阐述,不涉及屏幕适配。因此Android 老鸟不必栖息。

相关概念

DPI

DPI 全称为 Dots Per Inch. Inch的含义是英寸,意思也就是『一英寸内点的个数』。Android 设备采用 DPI 来表示屏幕的『密度』。DPI 原本是印刷行业的概念,意思是一英寸内有多少个墨点。

PPI

原本还有一个概念叫 PPI(Pixels Per Inch) 意思是『每英寸内的像素数』,这是通用的概念。

这两个概念在 Android 中是一样的含义,只不过在 Android 中采用了 DPI 这个不太严谨的概念,要表达的意思是一致的,还是一英寸中像素点的个数。

我们在买一个手机的时候经常会关心这个手机屏幕有多大,称之为多少寸,比如说 5.0 寸,4.7 寸。这个寸其实指的是屏幕对角线的长度,单位就是英寸(Inch)。也称为『物理尺寸』。1英寸等于 2.54cm。

分辨率

所谓的分辨率,是指的一个显示设备横纵方向各有多少个像素点。例如我们说一个手机的分辨率为 1280×720 ,指的就是这个手机纵向有 1280 个像素点,横向有720个像素点,整个手机屏幕上一共就有 1280×720个像素点。

如何计算手机屏幕的密度

由于手机屏幕硬件的差异性,屏幕中同样物理尺寸的一块空间,能包含的像素点的个数可能不一样。

以两款常见的手机为例:

手机型号 屏幕尺寸 分辨率
红米3s 5.0寸 1280*720
HUAWEI nova 5.0寸 1920*1080

通过上表可以明显发现,哪怕两个物理尺寸一模一样的手机,里面能容纳的像素点的个数都可能不一样。这种情况就可以称之为两个手机的屏幕密度不一样。根据 DPI 的概念。我们可以通过如下方式计算手机的屏幕的 DPI:

根据此计算公式可以得出两款手机的屏幕密度分别为:

手机型号 屏幕尺寸 分辨率 屏幕密度
红米3s 5.0寸 1280×720 294dpi(ppi)
HUAWEI nova 5.0寸 1920×1080 441dpi(ppi)

对比厂商提供的参数信息:

红米3s


HUAWEI nova

通过对比我们发现,我们计算的和厂商提供的完全一致,计算公式妥妥的!

另外两个概念

了解了 DPI 的计算方式,我们再来看两个大家很熟悉的概念。

PX

px 就是像素 pixel 的缩写。1px 代表一个显示设备中的一个像素点。

DP(DIP)

DP 或者 DIP 叫做(Density-Independent pixel),或者(Device-Independent Pixel),中文翻译为『密度独立像素』或者『设备独立像素』,Android 中的特有单位,它的大小是由操作系统根据手机屏幕密度动态渲染出来的,1dp 对应多少 px 在不同的设备上,可能是不一致的。

目前我们确实可以通过公式计算出屏幕密度了,又了解了 px 和 dp 的概念。各种概念,有好事的哥们可能急了,你特么到底要说啥?来,看一个常见的情景:

在 Android 开发中,我们经常需要设置一个控件的宽高。假设你采用的测试机为红米 3s,你希望一个控件的宽度占用屏幕的一半。于是将其宽度设置为 360px,运行结果在你的手机上没有问题。但是同样的代码,运行在 HUAWEI nova 的手机上,由于 nova 手机横向有 1080 个像素点,360 / 1080 = 0.33 明显只占据了屏幕的三分之一。

从上面的事例中我们可以发现,在 Android 中通过 px 作为单位设置一个控件的宽高,在不同屏幕密度的手机上必然会出现问题。这也是为什么会存在 dp(dip) 这个单位。这个问题我们先记着,一会儿再回过头来解决它。

PX 与 DP 之间的换算公式

px = dp * (dpi / 160)
dp = px / (dpi / 160)

上面的 dpi /160 也称之为density
看到这个公式可能有的同学会问了,公式中的 160 是个什么鬼?

在 Android 中,160dpi 的设备下, 1dp = 1px,也就说在160dpi 的设备中写 px 和写 dp 效果一样。至于原因,在Google的官方文档给出过解释,因为第一款 Android 设备( HTC 的T-Mobile G1)是属于(注意,是属于,不是等于)160dpi的。

为什么叫属于呢?如果按照 DPI 的计算公式,T-Mobile G1 应该为 180dpi。那么为什么它又属于 160dpi 呢?

看图:

Android 里把主流设备的 dpi 归成了四个档次,120 dpi、160 dpi、240 dpi、320 dpi,可以看出T-Mobile G1的参数属于mdpi区域的,以上就是取160dpi作为基准的原因。

搞清楚这个计算公式之后,回到之前的问题,既然设置 360 px 有问题,那么换成 dp 作为单位改设置为多少呢?根据公式:

dp = px / (dpi / 160)
196dp = 360 / (294 / 160)

在 DPI 为 294 分辨率为 1280×720 的红米手机上将宽度设置为 196dp 没有问题,在 DPI 为 441 分辨率为 1920×1080 的华为手机上有没有问题呢?根据公式:

px = dp (dpi / 160)
540px = 196
(441 / 160)

540px 正好也占据宽度 1080 的一半。相信现在大家对 Android 屏幕以及控件相关单位的概念已经差不多理解了。

为什么要使用 DP 作为单位呢?

以 DP 作为单位,系统会根据不同 DPI 的手机,将 DP 为单位的值转换为合适的 PX 目的就是为了界面中的控件,在不同 DPI 的手机上,显示效果保持一致,也就是让人看起来,控件的比例是一样大小的。

BUT!还没有完!

代码转换

开发中,布局文件中我们习惯使用 dp 单位,但是Android中绝大多数 java 代码的api中默认是以 px 作为单位(如 setPadding等),如果传入同样的值,在不同的手机上同样可能产生问题,因此我们在很多场景下需要进行 dp 和 px 的互转。

DP 转 PX

1
2
3
4
5
6
7
8
9
/** 
* 自动根据手机的 dpi 从 dp 的单位 转成为 px(像素)
* @param context 上下文
* @param dpValue 需要转换的 dp 值
*/

public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}

PX 转 DP

1
2
3
4
5
6
7
8
9
10
11
/** 
* 自动根据手机的 dpi 从 px(像素) 的单位 转成为 dp
*
* @param context 上下文
* @param pxValue 需要转换的 px 值
* @return
*/

public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}

上面代码中的

context.getResources().getDisplayMetrics().density; 

其实就是 dpi /160 的值。
至于结果为什么要加上 0.5f,你猜猜看?