android 屏幕那点事

转载自http://www.cnblogs.com/olvo/archive/2012/04/17/2453203.html


 

1.px: 像素, 如分辨率为240*320, 即为240px*320px.

2.dp=dip: 如果一个160dpi的屏幕,1dp=1px

3.上边说的dpi为dots per inch. 每英寸的点.dots是TM什么?我理解就是px.

4.sp: 

ppi和dpi经常都会出现混用现象。从技术角度说,“像素”(P)只存在于计算机显示领域,而“点”(d)只出现于打印或印刷领域。


Android布局单位及分辨率(dip,dp,sp,px)

  网上介绍Android布局单位的文章很多,但是我翻了不少,却发现大部分都是一个拷贝的版本,当然网络上也有不少是是根据个人使用习惯写的一些心得,最终经过整合后,walfred将这些很基础的知识给整合吸收了,所以这里会结合自己的理解将Android的布局单位的使用做一个简单的概括。

单位一览表

px:单位尺寸里的像素点

dp:一个基于density的抽象单位,如果一个160dpi的屏幕,1dp=1px

dip:等同于dp

sp:同dp相似,但还会根据用户的字体大小偏好来缩放,常用来作为字体大小的单位。

        这些单位我们都见过,但是真正拿到手上用的却(对walfred而言)用到较多的就是dip(dp)和sp呢,所以我会重点讲解这两个单位的。

像素px与屏幕密度density

        既然知道了像素是单位里面的像素点,我们就可以通过像素来求得屏幕密度,下面的公式即为像素与像素密度的换算关系:

pixs =dips * (densityDpi/160)

dips=(pixs*160)/densityDpi

        那我们平时使用的240*320像素的手机(WQVGA/QVGA)、320*480的手机(HVGA)及现在主流的480*800的手机(WVGA),我们都可以算出其屏幕密度,一般我们在计算时计算宽度就可以了,高度同理,所以就得出:

240*320像素的手机(WQVGA/QVGA)的density=120;

320*480的手机(HVGA)的density=160;

480*800的手机(WVGA)density=240;

        1、所以得出结论

        Android的屏幕密度是以160为基准的,屏幕密度(density)为160时,是将一英寸分为160份, 每一份是1像素;同理如果屏幕密度(density)为240时,是将一英寸分为240份,,每一份是1像素,所以近来的新的sdk为了适配不同的屏幕分辨率的机型,已经陆续取消采用像素px作为布局单位这主要是针对不同设备而言的。因为px不管在什么样的设备上都是那样长,但是dip会根据设备变化;

        2、谷歌的策略       

当屏幕density=240时使用hdpi标签的资源;

当屏幕density=160时,使用mdpi标签的资源 ;

当屏幕density=120时,使用ldpi标签的资源

        3、获取设备像素

        参见文章:获取手机屏幕分辨率

        4、单位代码换算

  Java代码
  1. public static int dip2px(Context context, float dipValue){   
  2.         final float scale = context.getResources().getDisplayMetrics().density;   
  3.         return (int)(dipValue * scale + 0.5f);   
  4. }   
  5.  
  6. public static int px2dip(Context context, float pxValue){   
  7.         final float scale = context.getResource().getDisplayMetrics().density;   
  8.         return (int)(pxValue / scale + 0.5f);   
  9. }   
  10.  
  11. public static int dip2px(Context context, float dipValue){   
  12.         final float scale = context.getResources().getDisplayMetrics().density;   
  13.         return (int)(dipValue * scale + 0.5f);   
  14. }   
  15.  
  16. public static int px2dip(Context context, float pxValue){   
  17.         final float scale = context.getResource().getDisplayMetrics().density;   
  18.         return (int)(pxValue / scale + 0.5f);   
  19. }   

观念入门:什么是DPI?

CoverDPI是个很熟悉又陌生的名词,举凡印表机、扫瞄器甚至是光学滑鼠都会看到它的踪影,但是你知道10元硬币可以用来目测DPI吗?还有如何透过简单的试算,知道萤幕解析度、滑鼠DPI、和游标移动距离的关系?

 

名词

  • 英:dots per inch

解释

其实DPI是dots per inch的缩写,也就是一英吋(2.54公分)的长度中,可容纳的点阵数。

如果将扫瞄的影像或印刷出来的纸张放大到一定的极限,能发现影像其实是由许多的点构成。在短短的一英吋长度中,如果可容纳300个点,解析度就是300dpi。同理可证,若有印表机解析度能到达600dpi,就代表它的影像品质是300dpi的两倍,因为在同样的长度中,能容纳600个墨点,影像细节自然也就更丰富。由此可知,在一张4×6英吋的相片纸上,300dpi与600dpi印表机印出的相片墨点分别是1200×1800与2400×3600,呈现出的影像当然是600dpi比较漂亮。

不过人眼可辨识的影像细节还是有极限,DPI当选购的参考就好,不需要过度追求高解像能力。

▲ 一英吋就是十元硬币直径

对一英吋没概念没关系,拿出十元硬币比比看就知道,十元硬币直径的长度上有多少点,DPI就是多少。

应用

光学滑鼠也很常用DPI这个规格,主要是标榜光学或雷射头的解像能力。假设萤幕解析度是800×600,滑鼠的DPI是800,当游标在桌面最右边时,你只需要把滑鼠左移一英吋,游标就会从最右边跑到最左边。

再把滑鼠的DPI提升到1600,这时把游标从右端移到左端,只需要把滑鼠移动0.5英吋。当滑鼠DPI值越高,在同样解析度的萤幕下,移动速度更快也更流畅。

▲ 滑鼠可达4000dpi

部分游戏滑鼠可显示DPI,目前最高可达4000dpi。一般来说800dpi的滑鼠就很实用,太高反而会不好操控。

换个角度想,当萤幕从15吋(解析度1024×768)升级到24吋(解析度1920×1200),滑鼠一样是400dpi。15吋的移动时只要2.56英吋(6.5公分),24吋却要4.8英吋(12.1公分)。

要移动超过12公分才能把游标从萤幕一端移到另一端,对电脑操作上实在太过吃力,因此升级高解析度萤幕的同时,最好能配上适当的滑鼠用起来才够顺手。

▲ 颗粒感重?检查相片和印表机

有些相片印出来颗粒感重,除了本身解析度不够外,也可能是列印品质太差,相片列印前要确定解析度可达300dpi以上。

 


Android手机屏幕那点事

首先,一块屏幕有几个参数,屏幕的物理尺寸,分辨率,像素密度(Density, DPI)。

其中

  • 物理尺寸,就是所说的几寸的屏幕,代表屏幕对角线的长度,比如3.5寸、3.7寸、4寸、7寸等。
  • 分辨率,是屏幕总共能显示的像素数,通常我们都说几百×几百,比如240*320,320*480,480*800等,我们一般直接说乘后的结果。
  • 像素密度(DPI),DPI的全称是dots per inch,每英寸点数,还有个词是PPI,是每英寸像素数,其实PPI感觉更准确一些,这两个比较纠结,很多时候混用,这里就不明确区分了。(本文的意思都是“每英寸像素数”)

这三个参数,任两个确定后,第三个量就是确定了。公式为:分辨率(总像素数)= 物理尺寸 × 像素密度

    解释一下: 像素密度就是每物理尺寸像素数, 一乘当然左边=右边,都是像素数嘛!

  • 比如一个3寸的屏幕,分辨率为240×320,那么密度为 开方(240^2+320^2)/3 约等于为133。
  • 再比如一个3.5寸的屏幕,分辨率为320x480,那么密度为 开方(320^2+480^2) / 3.5 约等于165.
  • 再比如一个3.5寸的屏幕,分辨率为480×800,那么密度为 开方(480^2+800^2)/3.5 约等于为194。
  • 在比如一个3.5寸的屏幕,分辨率为960x640,那么密度为 开方(960^2+640^2)/3.5 约等于329。
  • 再比如一个4寸的屏幕,分辨率为480x800,那么密度为 开方(480^2+800^2)/4 约等于233。

面对种类旁杂的屏幕,开发人员该怎么办,人工针对不同屏幕做相应调整,No!

让机器调整!开发人员是天生懒惰的!

那么要调整什么,目的该是让界面元素的物理大小在所有设备上保持一致(但是屏大的似乎天然可以显示的大一点,小屏的可以小一点。)

过去,开发人员通常以像素为单位设计计算机用户界面。例如,定义一个宽度为300像素的表单字段,列之间的间距为5个像素,图标大小为16×16像素等。这样处理的问题在于,如果在一个每英寸点数(dpi)更高的新显示器上运行该程序,则用户界面会显得很小。在有些情况下,用户界面可能会小到难以看清内容。

针对屏幕的三个参数,分析如下:

  • 同样物理尺寸,分辨率不同,那么如果按照像素设计,就会产生,分辨率大的那个,图像很小.物理尺寸就会很小.
  • 同样分辨率,不同物理尺寸,如果按钮找像素设计,实际看起来的物理比例是一样的.
  • 看起来物理尺寸一样,不同分辨率,分辨率大的,屏幕尺寸就要大.
  • 看起来物理尺寸一样,不同屏幕尺寸,大尺寸的,就要像素多.

那么Android框架为自动调整尺寸做了什么呢?

就是密度无关像素,原文如下

The density-independent pixel is equivalent to one physical pixel on a 160 dpi screen.

是说,以160dpi为标准,在一个160dpi的屏幕上的1个物理像素作为设备无关像素的1个像素,也就是Android最佳实践中推荐的dip/dp(以下这两个单位表示同样含义,dip常见于Google官方示例中)这个单位。

针对于字体,Android设计了sp这个单位,这个于dp的不同在于,字体大小在dp的基础上,可以根据用户的偏好设置,相应调整字体大小,所以是scale的。

但是!

Android的做法不是根据160dpi这个标准值和设备实际的dpi的比值进行缩放!而是设定了一套标准化尺寸和密度:

  • 标准化物理尺寸: small, normal, large, and xlarge
  • 标准化屏幕密度: ldpi (low), mdpi (medium), hdpi (high), and xhdpi (extra high)

Each generalized size or density spans a range of actual screen sizes or density. For example, two devices that both report a screen size of normal might have actual screen sizes and aspect ratios that are slightly different when measured by hand. Similarly, two devices that report a screen density of hdpi might have real pixel densities that are slightly different. Android makes these differences abstract to applications, so you can provide UI designed for the generalized sizes and densities and let the system handle any final adjustments as necessary. Figure 1 illustrates how different sizes and densities are roughly categorized into the different size and density groups.(摘自官方文档)

如图: 

screens-ranges

(我曾经以为,Android会根据实际dpi进行缩放,这也是我迷惑很久,之前写就在这个卡住了)

为了证明Android确实不是不是根据实际dpi进行缩放,我查阅了相关的源代码。

我们知道当显卡绘制一个图像的时候,是根据物理像素绘制的。所以,当开发人员设定dp这种单位的时候,需要一个转化过程,将sp转化为px。

如果按我之前所想,计算公式该是:实际dpi / mdpi(也就是160dpi)然后乘上sp的数值,这样就得到了在不同设备上物理大小完全一样的的界面元素。

但是Android不是这样设计的,正如前文所说,是根据那套标准化的密度来进行转换的。通过如下代码(这个是Android将dp转化为px值的过程)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static float applyDimension(int unit, float value,
                                   DisplayMetrics metrics)
{
    switch (unit) {
    case COMPLEX_UNIT_PX:
        return value;
    case COMPLEX_UNIT_DIP:
        return value * metrics.density;
    case COMPLEX_UNIT_SP:
        return value * metrics.scaledDensity;
    case COMPLEX_UNIT_PT:
        return value * metrics.xdpi * (1.0f/72);
    case COMPLEX_UNIT_IN:
        return value * metrics.xdpi;
    case COMPLEX_UNIT_MM:
        return value * metrics.xdpi * (1.0f/25.4f);
    }
    return 0;
}

可以看到,如果单位是dip(dp),那么返回值则是dip的value * metrics.density。

这里的density是

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.
This value does not exactly follow the real screen size (as given by xdpi and 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).
(摘自Google官方文档,懒得翻译了,当然也是怕翻译坏了原来的味道,这段还是相当重要的)

重点是This value does not exactly follow the real screen size。这也解释我之前的疑惑。

这个density值Displaymetrics记录的,如果你想看看实际情况,可以获取Displaymetrics,通过代码:

1
2
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);

然后就能得到metrics的值。

另外!

这类还有xdpi和ydpi这两个值,官方文档上说:The exact physical pixels per inch of the screen in the X(Y) dimension.

然而,当我试图获取某些机器的这两个值的时候却与我手动计算所得到的值完全不同!

后来翻阅StackOverflow,看到也有人遇到类似问题,

作者获得了几个设备的dip值,如下:

  • HTC Desire Z: 480x800, density : HIGH, xdpi: 254.0, ydpi: 254.0
  • Motorola Defy: 480x854, density : HIGH, xdpi: 96.0, ydpi: 96.0
  • Samsung Galaxy S2: 480x800, density : HIGH, xdpi: 217.71428, ydpi: 218.49463
  • LG Optimus 2X: 480x800, density : HIGH, xdpi: 160.0, ydpi: 160.0

(原文地址: http://stackoverflow.com/questions/6224900/android-incorrect-size-for-views-with-mm-or-inch-dimensions

可以看到对于Moto和LG的dpi是明显错误的。

再回想刚才Android转换单位的函数里面这段代码:

1
2
3
4
5
6
case COMPLEX_UNIT_PT:
    return value * metrics.xdpi * (1.0f/72);
case COMPLEX_UNIT_IN:
    return value * metrics.xdpi;
case COMPLEX_UNIT_MM:
    return value * metrics.xdpi * (1.0f/25.4f);

对于这几个单位的处理都用到了xdpi,所以很可能转换后是错误的值,
(这里应该仍然算是个疑问,难道真的没有办法得到正确的值吗?我们都知道是不推荐用pt,in,mm这种单位的,这是否也是一个方面)

至此关于屏幕的问题大体说完,然后就是提供的资源问题,当我们设置了一个界面元素的的大小后,对于不是标准dpi的机器上就要进行缩放,那么对于绘制的矢量元素,自然是不用管,而对于图像这种位图,缩放后会导致模糊等问题,所以就要对标准化dpi的几个大小,提供相应的替换版本,Android会根据实际屏幕规格,进行相应替换,并且有相应的查找资源的规则,看Android源码,可以知道,Android的框架的默认ui使用了大量nine-patch图片。这里就不详细说了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章