十分钟搞懂 Android 中的常用尺寸单位

2,614 阅读6分钟

本文详细介绍了Android开发中常用尺寸单位的含义,重点讲解了sp与dp这两个尺寸单位的本质以及它们与px的换算公式。文末提供了用一套资源适配各种分辨率手机的"秘籍":)

常见尺寸单位

Android开发中的常用尺寸单位有如下几种:

  • dp (dip)
  • px
  • pt
  • inch
  • sp

就算不知道确切含义,相信对于以上这几种尺寸单位大家也都比较脸熟,这里先让我们重新认识一下它们:

  • dp (dip): 即设备无关像素(device independent pixels),这种尺寸单位在不同设备上的物理大小相同。
  • px:即像素(pixel),这个不用多说。
  • pt:通常用来作为字体的尺寸单位,1 pt相当于1/72英寸。
  • inch:英寸,1 英寸约等于2.54厘米,主要用来描述手机屏幕的大小。
  • sp:大部分人只知道它通常用作字体的尺寸单位,实际大小还与具体设备上的用户设定有关。(如果你对"sp"的了解停留于此,那么看完这篇文章后你会更透彻的理解它^ _ ^)

在上面几种常见的尺寸单位,dp和sp可以看做是虚拟尺寸。其中dp是与设备无关的虚拟像素单位,开发者为UI控件指定以dp为单位的大小后,在不同屏幕密度的Android设备上便能够具有相同的物理尺寸。dp的出现让开发者无需关注屏幕密度、物理像素之间的换算关系。sp则与dp相似,但它主要用作字体的尺寸单位,与dp的区别是:Android系统支持用户设定字体大小,因而sp的实际大小还会根据用户设定在原基础上进行缩放。下面来详细介绍dp与sp这两种尺寸单位。

尺寸单位详解之dp

dp的全称是device independent pixels,在具有不同屏幕密度的设备上,1 dp的物理大小是相同的。那么,什么是屏幕密度呢?

屏幕密度

手机的屏幕密度通常指的是手机屏幕的dpi(dots per inch),也就是每英寸的像素数。对于Android手机来说,常见的dpi有如下几种:

  • ldpi:对应的dpi范围为0 ~ 120,也就是说每英寸有0到120个像素点的屏幕的屏幕密度都属于ldpi
  • mdpi:dpi范围为120 ~ 160
  • hdpi:dpi范围为160 ~ 240
  • xhdpi:dpi范围为240~320
  • xxhdpi:dpi范围为320~480

在实际开发中,通常以dpi值120、160、240、320、480分别指代ldpi、mdpi、hdpi、xhdpi、xxhdpi。通常屏幕密度越大的手机显示的图像会越细腻。可以通过如下代码获取当前Android设备的屏幕密度:

private void getDpi() {
    DisplayMetrics dm = getResources().getDisplayMetrics();
    Log.i("TAG", "density = " + dm.density);
    Log.i("TAG", "densityDpi = " + dm.densityDpi);
}

若我们在一台屏幕密度为320dpi的Android手机上运行以上代码,会得到如下输出:

density = 2
densityDpi = 320

上面输出中的densityDpi就是Android手机屏幕的dpi值,那么density是什么呢?实际上它代表的是当前屏幕的dpi值与基准dpi值的比值,这个基准dpi值为160。
现在我们已经理解了dpi,接下来让我们揭开dp的神秘面纱。

dp

上面我们提到了选择dpi值160作为基准屏幕密度,这个基准屏幕密度人为建立起了dp与px间的关系:在dpi为160的Android设备上,1 dp = 1px。假设x为某UI控件以px为单位的大小,y为同一UI控件以dp为单位的大小,densityDpi表示屏幕密度,则x与y的关系为:x = y * densityDpi / 160。
介绍完了dp,接下来让我们探究一下sp这个尺寸单位的真面目。

尺寸单位详解之sp

在介绍sp之前,我们先来一起看下TypedArray类中包含的一个用户将dp、sp等单位转换为px的静态方法:

    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;
    }

若要将dp转换为px,会执行如下代码:

return value * metrics.density;

density我们在前面介绍过,指的是当前dpi与基准dpi(160)的比值。density的计算方式就是当前屏幕的dpi除以160。也就是说,在屏幕的dpi为120、160、320、480时,density的值分别为0.75、1、2、3。
若要将sp转换为px,则会执行如下代码:

return value * metrics.scaledDensity;

可以看到,sp转换为px的计算公式与dp转换为px时相似,那么scaledDensity是什么呢?实际上,scaledDensity不同于density,scaledDensity是可以动态改变的,当用户改变了Android设备的字体缩放比例时,scaledDensity的值就会发生变化。scaledDensity的计算公式为:scaledDensity = density * fontScale。其中fontScale代表用户设定的Android设备字体缩放比例,默认为1。也就是说,当用户没有改变Android设备的字体缩放比例时,sp、dp与px的换算是相同的。

多分辨率之殇

市面上存在着的各种不同分辨率的Android设备为广大Android开发者挖了众多的坑,比如:

  • 需要为不同分辨率的Android设备单独维护一套dimens文件;
  • 通常UI设计师只会针对某种特定分辨率的设备为我们标注UI控件的像素大小,相信不少小伙伴都受够了手动换算不同分辨率设备上UI控件像素大小的痛苦;
  • 通常我们需要为每种分辨率的Android设备维护一个drawable文件夹以获得比较好的图片显示效果,这会导致apk文件尺寸的臃肿;而且若某个drawable文件夹下的图片需要修改,那么就需要替换其他所有drawable文件夹中对应的图片。如果不小心漏掉了某个drawable文件夹下的图片,则会导致该图片在某些分辨率的手机上失真。

针对上面的多分辨率适配之坑,感谢谷哥的小弟提供了完整的解决方案,该解决方案已经被运用到项目中并取得了很好的效果,欢迎大家前往这里观摩学习:)


长按或扫描二维码关注我们,让您利用每天等地铁的时间就能学会写出优质app。