浅谈屏幕适配 dp dip sp dpi ppi px sp

一、Drawable资源文件夹之间的关系
Android开发中,UI一般会制作多种图片素材,根据素材的分辨率大小,放在如下几个文件夹中:
Android 工程里 res 目录下的 drawable-hdpi, drawable-xhdpi, drawable-xxhdpi 等文件夹。我们知道 Android 会根据屏幕的 dpi 去选择对应的 drawable 文件夹,Android项目的资源文件下存在以下目录:

drawable-ldpi ( 当dpi为120时,使用此目录下的资源),如QVGA (240x320)
drawable-mdpi ( 当dpi为160时,使用此目录下的资源),如HVGA (320x480)
drawable-hdpi ( 当dpi为240时,使用此目录下的资源),如WVGA (480x800),FWVGA (480x854)
drawable-xhdpi ( 当dpi为320时,使用此目录下的资源),如720P:1280x720(标清,standard definition,SD)
drawable-xxhdpi ( 当dpi为480时,使用此目录下的资源),如1080P:1920x1080(高清,high definition,HD)
drawable-xxxhdpi ( 当dpi为640时,使用此目录下的资源)
为基准可以看出系数比例关系:0.75:1:1.5:2:3:4

Android 正是根据设备DPI值得不同,选择清晰度不同的资源使用,完成屏幕的适配, 但手机屏幕千奇百怪,因此上述文件夹不是指定具体的分辨率,而是一个范围:

drawable-ldpi(value <= 120 dpi)
drawable-mdpi(120 dpi < value <= 160 dpi)
drawable-hdpi(160 dpi < value <= 240 dpi)
drawable-xhdpi(240 dpi < value <= 320 dpi)
drawable-xxhdpi(320 dpi < value <= 480 dpi)
drawable-xxxhdpi(480 dpi < value <= 640 dpi)

所以 420 dpi 会优先加载 xxhdpi 中的资源文件,如果对应 dpi 目录下没有找到该资源文件,遵循先高再低原则,然后按比例缩放图片
举个例子:当前为 xhdpi 设备,则 drawable 的寻找顺序为:
xhdpi -> xxhdpi -> xxxhdpi (直到最高dpi资源文件目录都没有该资源文件) -> nodpi (如果低dpi有) -> hdpi -> mdpi
1.在高dpii资源文件目录找到
如果在 xxhdpi 中找到目标图片,则压缩(480/320) =2/3 使用
2.在低dpii资源文件目录找到
如果在 mdpi 中找到图片,则放大 (240/120)=2 倍使用。

二、分别率级别
换算关系

ldpi 的手机 1dp=0.75px
mdpi 的手机 1dp=1.0px
hdpi 的手机 1dp=1.5px
xhdpi 的手机 1dp=2.0px
xxhdpi 的手机 1dp=3.0px

在这里插入图片描述
三、 dp dip sp dpi ppi px sp
1.px (pixels)像素,就是屏幕上实际的像素点单位,也是屏幕物理上的最小显示单位。比如我的手机分辨率是1080*1920,就是宽度有1080个像素点,高度有1920个像素点。不同的设备不同的显示屏显示效果是相同的,这是绝对像素,不会改变。像素的大小是没有固定长度的,不同设备上一个单位像素色块的大小是不一样的。尺寸面积大小相同的两块屏幕,分辨率大小可以是不一样的,分辨率高的屏幕上面像素点(色块)就多,所以屏幕内可以展示的画面就更细致,单个色块面积更小,而分辨率低的屏幕上像素点(色块)更少,单个像素面积更大,可以显示的画面就没那么细致。

2.ppi (pixels pe inch),就是每英寸有多少个像素。比如一个手机是420像素/英寸。根据定义可以知道,同一个屏幕,同一个分辨率,这个值是固定的。关于ppi和dpi这两个措辞的差别,表面上看来只是 dot和 pixel,但实际上 dot 可以是打印的墨点,可以指扫描仪的采样点,可以指数字图像的最小单位(即 pixel),可以指屏幕的物理像素,可以指操作系统的抽象像素……在不同的语境下可以指不同的概念,而同样 pixel 也可以指数字图像的数据 pixel,可以指屏幕物理像素,也可以指代操作系统的抽象像素……在不同语境下的意义也不同。这两个单位完全就是时常混用的,在电子屏幕显示中提到的 ppi 和 dpi 可以认为是一样的,所以你可以忽略在措辞上用 dpi 或者 ppi 有什么不同,不过在 Android 平台上常用 dpi 这种表述方式。
在这里插入图片描述

3.dpi(Dots Per Inch)是屏幕像素密度。就是1英寸上像素点的个数。所以这个和 ppi 其实是一样的东西。常见的数值有160像素/英寸,240像素/英寸。对于屏幕来说,dpi越大,屏幕的精细度越高,屏幕看起来就越清楚。也就是当设备的dpi为160的时候1px=1dp;实际上dpi的计算是通过勾股定理算出屏幕对角的像素数再除以对角的尺寸就是最终的dpi。
以手机举个例子:
尺寸:5.0英寸(一般所说的尺寸就是机器的对角线尺寸。)
分辨率:1080*1920
DPI = sqrt((长度像素数² + 宽度像素数²)) / 屏幕对角线英寸数
tip:sqrt为开平方根

4.sp (scaled pixels-best for text size):类似dp,放大像素(比例像素),与刻度无关,可以根据用户的字体大小首选项进行缩放,主要用来处理字体的大小。 主要处理字体的大小。sp由于是放大像素,主要是用于字体显示,由此根据google的建议,TextView的字体大小最好用sp做单位。

5.density:density表示每英寸有多少个显示点(逻辑值),它的单位是dpi。谷歌规定,一块160像素/英寸的屏幕,密度就是1。那么320像素/英寸,密度就是2。也就是说:密度 = dpi / 160像素/英寸。

6.dip (density independent pixels)设备独立像素,大概就是一个抽象的像素,像素是一个具体的不变的东西,但是dip会根据屏幕的像素密度变化。不同设备有不同的显示效果,这个和设备硬件有关,一般我们为了支持WVGA、HVGA和QVGA 推荐使用这个,不依赖像素。
dip的换算: dip(value)=(int) (px(value)/1.5 + 0.5)

7.dp(Density-independent pixel)设备独立像素 就是dip,一种基于屏幕密度的抽象单位,在每英寸 160 点得显示器上,1dip = 1px,但随着屏幕密度的改变,dip 与 px 的换算会发生改变。因为 不同设备中有不同的显示效果,所以 为了解决在不通分辨率手机上运行不至于相差太大的问题,引入了 dip 计量单位,这种计量单位与移动设备硬件无关。当dpi=160像素/英寸,则1dp=1px。当dpi=320像素/英寸,则1dp=2px。标准是160dip.即1dp对应1个pixel,屏幕密度越大,1dp对应的像素点越多。
dp是与密度无关,sp除了与密度无关外,还与scale无关。如果屏幕密度为160,这时dp和sp和px是一样的。1dp=1sp=1px,但如果使用px作单位,如果屏幕大小不变,而屏幕密度变成了320。那么原来控件的宽度设成160px,在密度为320的屏幕里看要比在密度为160的屏幕上看短了一半。但如果设置成160dp或160sp的话。系统会自动将width属性值设置成(dp = (DPI/(160像素/英寸))px = density px)320px的。也就是160 * 320 / 160。其中320 / 160就是density。也就是说,如果使用dp和sp,系统会根据屏幕密度的变化自动进行转换。 dp也是谷歌推荐的android开发中使用单位。

8.像素密度和分辨率是两个不同的概念,分辨率是总的像素点,像素密度是单位长度的像素点
HVGA屏density=160;QVGA屏density=120;WVGA屏density=240;WQVGA屏density=120
VGA:Video Graphics Array,即:显示绘图矩阵,相当于640×480
HVGA:Half-size VGA;即:VGA的一半,分辨率为480×320;
QVGA:Quarter VGA;即:VGA的四分之一,分辨率为320×240;
WVGA:Wide Video Graphics Array;即:扩大的VGA,分辨率为800×480像素;
WQVGA:Wide Quarter VGA;即:扩大的QVGA,分辨率比QVGA高,比VGA低,一般是:400×240,480×272

9.计算 dp 与 px
换算公式如下:
dp = (DPI/(160像素/英寸))px = density px
注意,这里都是带单位的。px是单位,dp是单位,density没单位。
为了方便,假设dpi是240 像素/英寸 , 那么density就是1.5
那么就是 dp=1.5px ,注意这是带了单位的,也就是 设备无关像素 = density 像素
那么转换为数值计算的话,应该是下面这个式子
PX = DP * (density / 160)
像素值 = density * 设备无关像素值
使用 dp(sp) 能够保证显示质量,但不保证显示尺寸比例。
相反,使用 px 能够保证显示尺寸比例,但不保证显示效果。
注意:在 160dip 屏幕上,px 和 dp 是等价的。

10.获取方式

val metrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(metrics)
Log.i("density", metrics.density)// 2.625
Log.i("Dpi", metrics.densityDpi)// 就是dpi
Log.i("widthPixels", metrics.widthPixels)// 屏幕实际宽度
Logs.i("heightPixels", metrics.heightPixels)// 这里的值是除掉状态栏高度,屏幕实际高度要加上状态栏高度。
Log.i("scaledDensity", metrics.scaledDensity)// 默认就是density,除非用户手动改过
Log.i("xdpi", metrics.xdpi)// 
Log.i("ydpi", metrics.ydpi)// 

四、常规的安卓手机分辨率及其dpi和density的计算
Android Studio自带的模拟器设备:
在这里插入图片描述
手机屏幕的dpi和density的计算:
在这里插入图片描述
手机大小5.0英寸,分辨率为1080×1920,那么该手机屏幕的:
物理宽度:(√ ̄(1080×1080+1920×1920))/5.0
像素密度dpi:(√ ̄(1080×1080+1920×1920))/5.0=(约等于)440px/英寸
密度density:440/160=(约等于)2.754

五.px,dp,sp转换源码分析

Android developer的kotlin源码解释

open static fun applyDimension(
    unit: Int, 
    value: Float, 
    metrics: DisplayMetrics!
): Float

(1)Converts an unpacked complex data value holding a dimension to its final floating point value. The two parameters unit and value are as in TYPE_DIMENSION.
翻译:将保存维度的未打包复杂数据值转换为其最终的浮点值。 两个参数单元和值如TYPE_DIMENSION中所示。
(2)三个输入参数解释
1.uint
Int: The unit to convert from. Value is android.util.TypedValue#COMPLEX_UNIT_PX, android.util.TypedValue#COMPLEX_UNIT_DIP, android.util.TypedValue#COMPLEX_UNIT_SP, android.util.TypedValue#COMPLEX_UNIT_PT, android.util.TypedValue#COMPLEX_UNIT_IN, or android.util.TypedValue#COMPLEX_UNIT_MM
翻译:Int:要转换的单位。 值是android.util。
TypedValue # COMPLEX_UNIT_PX android.util。
TypedValue # COMPLEX_UNIT_DIP android.util。
TypedValue # COMPLEX_UNIT_SP android.util。
TypedValue # COMPLEX_UNIT_PT android.util。
TypedValue # COMPLEX_UNIT_IN或android.util.
TypedValue # COMPLEX_UNIT_MM
hint: 这些值在下面的图片也可看到

2.value
Float: The value to apply the unit to.

3.metrics
DisplayMetrics!: Current display metrics to use in the conversion – supplies display density and scaling information.

4.Return(返回值)
Float The complex floating point value multiplied by the appropriate metrics depending on its unit.
翻译:复杂浮点值乘以取决于其单位的适当度量。
这是TypeValue定义的参数
java源码解释

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和sp的区别在于density和scaledDensity两个值上;

变量density,
float类型,display的logic-density。是一个scaling-factor,用在Density-Independent-Pixel单位,一个dip就是一个像素。
160dpi的screen提供系统display的baseline。
因此,160dpi的screen-density值为1(160/160),120dpi的screen-density值为0.75(120/160)。
screen-1,已知240x320,1.5”x2” ,可以计算出densityDpi等于160。即240/1.5=160,或320/2=160。再通过densityDpi/160计算出density的值1.0。
screen-2,已知320x480,1.5”x2”,可以计算出densityDpi等于240。即320/1.5=240,或480/2=240。再通过densityDpi/160计算出density的值1.5。

–关于scaledDensity
float类型,一个scaling-factor,用于fonts显示,同density相同的值,除非由于基于font-size上的体验需要做微调。
–TyuMainApp.getApp().getResources().getDisplayMetrics()对象中的属性值

总结:
dp只跟屏幕的像素密度有关;
sp和dp很类似但唯一的区别是,Android系统允许用户自定义文字尺寸大小(小、正常、大、超大等等),当文字尺寸是“正常”时1sp=1dp=0.00625英寸,而当文字尺寸是“大”或“超大”时,1sp>1dp=0.00625英寸。类似我们在windows里调整字体尺寸以后的效果——窗口大小不变,只有文字大小改变。

六、转换公式dp和px之间的换算

 /**
     *
     * @param context 上下文
     * @param values dp值
     * @return
     */
   public static int dp2px(Context context, int values) {
        float scale = context.getResources().getDisplayMetrics().density;
        return (int) (values * scale + 0.5f);
    }

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

     public static int px2sp(Context context, float pxValue) {  
         float fontScale = context.getResources().getDisplayMetrics().scaledDensity;  
         return (int) (pxValue / fontScale + 0.5f);  
   }  

     public static int sp2px(Context context, float spValue) {  
         final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;  
          return (int) (spValue * fontScale + 0.5f);  
   }
//+0.5 就是为了在精度丢失的情况下进行四舍五入。
从代码中也可以看出 sp 和 px 间转换使用的是 scaledDensity,而 dp 和 px 间转换使用的是 density,也就是 sp 会随着系统字体设置缩放,dp 不会。

以dp2px为例,这里有一个小细节,根据上面的推导,return的结果应该是 (int) (values * density),而结果会+0.5f再强转,原因是浮点型强转整型过程直接回去除小数部分,+0.5f相当于四舍五入的过程(小数部分大与0.5的+0.5f后会进一位),结果显得更加精确。

七、标准dpi = 160
  (1)Android Design [1] 里把主流设备的 dpi 归成了四个档次,120 dpi、160 dpi、240 dpi、320 dpi
  实际开发当中,我们经常需要对这几个尺寸进行相互转换(比如先在某个分辨率下完成设计,然后缩放到其他尺寸微调后输出)。
  也就是说如果以 160 dpi 作为基准的话,只要尺寸的 DP 是 4 的公倍数,XHDPI 下乘以 2,HDPI 下乘以 1.5,LDPI 下乘以 0.75 即可满足所有尺寸下都是整数 pixel 。
  但假设以 240 dpi 作为标准,那需要 DP 是 3 的公倍数,XHDPI 下乘以 1.333,MDPI 下乘以 0.666 ,LDPI 下除以 2
  而以 LDPI 和 XHDPI 为基准就更复杂了,所以选择 160 dpi
(2)这个在Google的官方文档中有给出了解释,因为第一款Android设备(HTC的T-Mobile G1)是属于160dpi的。

八、布局时使用dp,sp还是px
  因为存在着很多不同屏幕密度的手机,屏幕密度就是dpi,就是单位长度里的像素数量。
  想象一下,如果这些手机的尺寸一样,屏幕密度相差很大。那我们画同样pix数量的时候,它显示的长度就会不一样,那是不是就要进行屏幕适配呢?
  1.sp 因为sp可以根据用户的字体大小首选项进行缩放,主要用来处理字体的大小。当你需要根据用户设置字体大小来动态改变APP的字体大小时,比如专门为老人手机设计的app,就建议用sp。虽然sp也是谷歌主推的android开发中使用单位。但会出现一个致命的问题,你设计的UI布局可能会随着字体的改变导致已经设计好的精美布局不可控,导致不好的用户体验(比如相同的控件在不同系统字体大小改变下控件的大小会随着改变)。
  2.dp 因为使用dp系统会根据屏幕密度的变化自动进行转换,所以在不同手机上控件的大小都一样,所以对于我们开发者而言是可控的。
  3.px 建议:不建议使用
九、人生感触
  其实人总是希望控制别人,或者换一个说法,人们喜欢可控的东西。就像Kotlin语言一样,你能够用java写的就不会用kotlin。因为你熟悉java,你主观认为java可控,但何时想过曾经java也很陌生,经过了从陌生到了解到熟悉的学习过程,你就会主观的认为java可控,希望能够跳出舒适圈,让自己不可控,才能创新。就像机器人一样,一方面希望生产出能够服务人类智能的机器人,但又担心机器人发展到“太智能”达到人们不可控的地步,但其实当机器人真正达到人们心中的“智能”,它本来就应该有自己的思维和思考,它本身就是不可控的。首先我的建议是把握自己主观认为可控,尝试自己客观认为不可控的。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>