好好理解Android的px,dp/dip,dpi,xdpi,ydpi,density这些参数!!!

1,536 阅读13分钟

声明:以下博客都是摘抄,加上自己的理解,不一定准确,如果有任何错误,欢迎评论教教我。

1.PX(像素)Pixel

即传统计算机语言中描述的像素,在Android则代表绝对像素。可以理解为屏幕上每一块的小点,屏幕最基本的单位。一般点击屏幕可以记录的坐标也是根据这个每一个小点来的。是整个屏是多少点,它是软件的显示单位。

  • 屏幕分辨率 640*960 举个例子,每个小格子都是一个像素,640就是设备宽有640个小格子,960就是设备高有960个小格子

image.png

之所以Android中不推荐使用这种单位,正是因为不同生产厂商,不同品牌,不同屏幕的设备,其分辨率亦不一。比如相同尺寸的设备,分辨率越高,显示和处理就越精细。 比如,我们设置一个控件为120px。

在相同的屏幕尺寸下:

  • 在分辨率的宽为320px的设备上,控件占设备屏幕3/8的宽度
  • 在分辨率的宽为640px的设备上,控件占设备屏幕3/16的宽度,宽度为上面的一半

一个像素的尺寸是和分辨率有关的,例如分辨率是72dpi的话,也就是每英寸里有72个像素,每像素就是1/72英寸.如果精度是300dpi的话,那每像素就是1/300英寸了,1英寸等于2.54厘米!


2.Dpi (Dots Per Inch)--每英寸的像素个数

也有人讲dpi称为“屏幕密度”。其含义则是:每英寸所打印的点数,既每一英寸的屏幕所包含的像素数。

举例来说,假设现在有一台“宽2英寸,高3英寸”的设备,则:

  • 当该设备分辨率为“320*480”,则dpi值为160。
  • 当该设备分辨率为“640*960”,则dpi值为320。

而“dpi”值越高的设备,其屏幕显示画面的效果也就越精细。

使用场景:

正是因为dpi值其代表的特性,所以android项目的资源文件下存在以下目录:

drawable-ldpi    ( 当dpi为120时,使用此目录下的资源)
drawable-mdpi    ( 当dpi为160时,使用此目录下的资源)
drawable-hdpi    ( 当dpi为240时,使用此目录下的资源)
drawable-xhdpi   ( 当dpi为320时,使用此目录下的资源)
drawable-xxhdpi  ( 当dpi为480时,使用此目录下的资源)
Android正是根据设备DPI值得不同,选择清晰度不同的资源使用,完成屏幕的适配。

3.dp (device independent pixels):

与我们之前谈到的绝对密度“px”对应,Android中引入的“dp”代表的则是 “设备独立像素

该单位是为支持WVGA、HVGA和QVGA而使用的,其不再依赖像素本身,而是和屏幕密度相关。

在Android当中规定:在屏幕密度为“160dpi”的情况下,则刚好“1dp = 1px”。

注:当屏幕密度为“320dpi”时,则“1dp = 2px”,以此类推.......

也正是因此,让我们得以保证了:控件在不同密度的屏幕上显示一致,既完成屏幕适配。

使用场景:

让我们回到上面说到的使用px造成的控件显示问题,此时我们将使用新的单位“dp”。于是:

在分辨率320480(既dpi为160)的设备下,则160dp等价于160px,按钮占屏幕宽的一半。
在分辨率640
960(既dpi为320)的设备下,则160dp等价于320px,按钮依然占屏幕宽的一半。

4.Density --密度

Density:

就这个单词本身直接翻译的意思而言,其也代表“密度”。 但需要注意的是,在Android中,其实并非如此。
注意我们这里指的是,通过代码“context.getResources().getDisplayMetrics().density”获取的“density”值。
而通过该方法获取到的该值,实际上是等价于“dpi / 160”的一个结果值。也就是说:
“getResources().getDisplayMetrics().density” = “getResources().getDisplayMetrics().densityDpi / 160”

看到这样一个解析,聪明的人大概已经能预见什么了。我们似乎发现了某种关联:
在Android里:“dpi = 160,则1dp = 1px”、“dpi = 320,则1dp = 2px”。以此类推。
到此你已经发现,dp,px与160之间存在着某种规律:“1dp = (dpi / 160)px”
换算一下,最终得到公式: dp = density * px

到了这里我们明白了,其实Android提供的该值,也就是为了让我们在dp与px之间做转换。
归根结底,其目的还是为了帮助我们做屏幕适配。

使用场景:

虽然使用dp在xml文件中定义控件尺寸,能够很好的帮助我们完成适配。
但很多时候,我们也会需要在Java代码中动态的去设定控件的尺寸。
但由于在代码中的尺寸设定,基本都被默认为了px单位。
所以这个时候就可以借助“density”来帮我们完成dp与px的转换,从而完成适配。

例如设置120dp的宽,那么就需要获取设备的density ,用120 * density 就得到我们在不同设备上需要的px的值。


5.xDpi ,yDpi

  • xDpi是水平方向上1inch实际上容纳的点的数量
  • yDpi是垂直方向上1inch实际上容纳的点的数量

为什么会写到这个呢,因为我在获取设备参数是看到了以下的信息,就有些疑问

//在MainActivity.java中
try {
    //获取屏幕分辨率
    WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();
    Point point = new Point();
    Display.class.getMethod("getRealSize", new Class[]{Point.class}).invoke(display,point);
    System.out.println("屏幕像素\t宽;"+point.x+"\t高:"+point.y);
    //获取屏幕密度
    DisplayMetrics localDisplayMetrics = new DisplayMetrics();
    display.getMetrics(localDisplayMetrics);
    System.out.println("Display相关信息:"+display.toString());
    System.out.println("DisplayMetrics相关信息:"+localDisplayMetrics.toString());

    System.out.println("desti:"+localDisplayMetrics.densityDpi);
} catch (Exception e) {
    e.printStackTrace();
}

得到的日志信息:

屏幕像素 宽;720 高:1280

Display相关信息:Display id 0: DisplayInfo{"内置屏幕", uniqueId "local:0", app 720 x 1184, real 720 x 1280, largest app 1196 x 1134, smallest app 720 x 670, 60.0 fps, supportedRefreshRates [60.0], rotation 0, density 320 (160.421 x 160.157) dpi, layerStack 0, appVsyncOff 0, presDeadline 17666667, type BUILT_IN, state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS}, DisplayMetrics{density=2.0, width=720, height=1184, scaledDensity=2.0, xdpi=160.421, ydpi=160.157}, isValid=true

DisplayMetrics相关信息:DisplayMetrics{density=2.0, width=720, height=1184, scaledDensity=2.0, xdpi=160.421, ydpi=160.157}

那我就非常的疑惑了,为什么 Display的density是320,但是xdpi变成了160.421. 我就去搜索像素和显示点 点(Dot)与像素(Pixel)的区别:

DPI中的点(Dot)与图像分辨率中的像素(Pixel)是容易混淆的两个概念,DPI中的点可以说是硬件设备最小的显示单元,而像素则既可是一个点,又可是多个点的集合。

image.png

然后去看源码中的释义

image.png

。。。弄了很久之后,在看了一篇博客之后明白了。这里的320是指的,系统默认的,并不是我们设备真实的dpi。

参考链接:blog.csdn.net/qq_41466437…

当然,我要复制一份,免得又忘记了。 Android开发中dip,dpi,density,px等详解 首先,解释一下各种单位:

.Px (Pixel像素) 也称为图像元素,是作为图像构成的基本单元,单个像素的大小并不固定,跟随屏幕大小和像素数量的关系变化(屏幕越大,像素越低,单个像素越大,反之亦然)。所以在使用像素作为设计单位时,在不同的设备上可能会有缩放或拉伸的情况。

.Resolution(分辨率) 是指屏幕的垂直和水平方向的像素数量,如果分辨率是 1920*1080 ,那就是垂直方向有 1920 个像素,水平方向有 1080 个像素。

.Dpi(像素密度) 是指屏幕上每英寸(1英寸 = 2.54 厘米)距离中有多少个像素点。如果屏幕为 320*240,屏幕长 2 英寸宽 1.5 英寸,Dpi = 320 / 2 = 240 / 1.5 = 160。

.Density(密度) 这个是指屏幕上每平方英寸(2.54 ^ 2 平方厘米)中含有的像素点数量。

.Dip / dp (设备独立像素) 也可以叫做dp,长度单位,同一个单位在不同的设备上有不同的显示效果,具体效果根据设备的密度有关,详细的公式请看下面

计算规则 以一个 4.95 英寸 1920 * 1080 的 nexus5 手机设备为例: 4.95英寸为左上角到右下角即直角边长度

Dpi :(像素密度) 计算直角边像素数量: 19202+10802=2202^2(勾股定理)。 计算 DPI:2202 / 4.95 = 445。 得到这个设备的 DPI 为 445 (每英寸的距离中有 445 个像素) Density:(密度) 上面得到每英寸中有 445 像素,那么 density 为每平方英寸中的像素数量,应该为: 445^2=198025。

Dip/dp:(设备独立像素) 先明白一个概念,所有显示到屏幕上的图像都是以 px 为单位。

Dip 是我们开发中使用的长度单位,最后他也需要转换成 px。

计算这个设备上 1dip 等于多少 px: px = dip x dpi /160 px = 1 x 445 / 160 = 2.78

注:android中以分辨率为320x480,即dpi为160为基准值,dpi为160时,1px = 1dp

通过上面的计算可以看出在此设备上 1dip = 2.78px,那么这是一个真实的故事吗? nonono,其中的关键值 dpi 并不是我们算出来的 445 ,请往下看。

Android 系统定义的 Dpi 在上面计算中我们得到一个 4.95 英寸 1920 * 1080的手机dpi为445。但是android有很多的分辨率,而且即使是同一个分辨率,在不同尺寸的手机中dpi也不相同,很麻烦,为例解决这个问题,Android 中内置了几个默认的 Dpi ,在特定的分辨率下自动调用,也可以手动在配置文件中修改。

ldpi mdpi hdpi xhdpi xxhdpi 分辨率 240x320 320x480 480x800 720x1280 1080x1920 系统dpi 120 160 240 320 480 基准比例 0.75 1 1.5 2 3 这是内置的 Dpi ,啥意思? 在 1920*1080 分辨率的手机上 默认就使用 480 的 dpi ,不管的你的尺寸是多大都是这样,除非厂家手动修改了配置文件,这个我们后面再说。 我们亲自尝试一下:

<TextView    
  android:id="@+id/tv"   
  android:layout_width="200dp"    
  android:layout_height="100dp"    
  android:text="Hello World!" />

这是一个宽200dp,高100dp的textView,按照我们之前的公式手动计算:

  • height = 100 x 445 / 160 = 278.5px
  • width = 200 x 445 / 160 = 556.25px

根据dpi为445计算出的px为高278.5px 宽556.25px

之后用代码验证一下: layout = (RelativeLayout)findViewById(R.id.la); //要在控件绘制完成后才能获取到相关信息,所以这里要监听绘制状态

layout.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()     {    
  public boolean onPreDraw() {        
    Log.d("hehehe", textView.getHeight() + "/" + textView.getWidth());       
    return true;    
  }
});

输出的结果为:300/600,可见,在内部高画了300px,宽画了600px

所以重新推导一下计算:

  • height = 100 x 480 / 160 = 300px
  • width = 200 x 480 / 160 = 600px

其中的 160 是基准值不会变的, 100 和200 是我们设置的 dp ,那么这 480 是从何而来的?说好的 445 呢? 找到我们手机中的 /system/build.prop 文件,其中有一行是这样: ro.sf.lcd_density=480

这就指定了这个机型使用的dpi是多少,还有一种情况是没有这一行(我在模拟器中发现过),那么应该是根据表格中的分辨率来自动设置。

我更改这行为: ro.sf.lcd_density=320

再次运行上面的测试代码,输出结果为:200/400

再次推导一下计算:

height = 100 x 320 / 160 = 200px width = 200 x 320 / 160 = 400px 1 2 说到底,因为有dpi这个动态的系数,我们在使用dp的时候才能兼容不同分辨率的设备。

到这里,应该都明白了 dpi 的由来,以及系统 dpi 跟物理 dpi 并不一定相同。在系统中使用的全部都是系统 dpi,没有使用物理 dpi,也获取不到物理 dpi。物理 dpi 主要用于厂家对于手机的参数描述(也可以看做 ppi )!

然后。。表格中还有一个东西叫做基准比例,这个其实就是计算 dp -> px 中重要的系数,以 160 为基准,其他的除以 160 得到比例。

以 160 为基准:dpi的越大1dp转换为px的值也就越大 总结:

  1. dpi(每英寸像素数)是有预设值的!120-160-240-320-480。对应不同的分辨率。

  2. 基准比例 = dpi(每英寸像素数) / 160

  3. px = dp x 基准比例

我们主要使用的类是:DisplayMetrics

以下为官方api说明

//A structure describing general information about a display, such as its size, density, //and font scaling.
//To access the DisplayMetrics members, initialize an object like this:
DisplayMetrics metrics = newDisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);

这是一个获取屏幕信息的类,比如大小,密度等。以及初始化的方法。

实际运用如下:

DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
//通常我们在使用DisplayMetrics时,都是直接获取内部变量来使用。所以下面直接列出各个内部变量。

dm.ydpi;     //得到物理屏幕上 Y 轴方向每英寸的像素
dm.xdpi;     //得到物理屏幕上 X 轴方向每英寸的像素 
             //ps:  其实这两个大多数情况下都是相同的
             //你能想象上面像素密度大很清晰 下面密度小跟马赛克一样吗 233333

dm.density;           //获取当前设备的基准比例
dm.densityDpi;        //获取系统dpi,随着 build.prop 文件中的代码而改变。

dm.widthPixels;       //获取屏幕宽度的像素数量

//获取屏幕高度的像素数量! //注意 - 因为这里会自动减去32dp的像素数量,根据分辨率不同的设备,减去的像素数量也不同,但是可以根据公式推算完整(px = dp x 基准比例)。 /为啥不用dm.densityDpi / 160 得到基准比例? 因为那个会随着build.prop文件代码变更而更改,算出来的不一定准确/ dm.heightPixels + 32 * dm.ydpi / 160;


声明:以上博客都是摘抄,加上自己的理解,不一定准确,如果有任何错误,欢迎评论教教我。