前言
好久之前就想写一篇跟屏幕适配相关的文章一直没有动笔,这次下决心抽周末的时间结合我在实际项目中所遇到的问题写一篇博客。
Android屏幕组成
Android手机屏幕是由很多的像素点(pixels)组成的,从左到右称为x轴,从上到下为y轴。屏幕的分辨率就是屏幕上x轴上的像素点格式乘以y轴上的像素点个数。320*480的屏幕就是有320个像素点在x轴上,有480个像素点在y轴上。如下图所示:
Android屏幕大小
什么是屏幕的大小? 对于手机,平板,电脑,或者电视,通常用屏幕的对角线的长度单位为英尺(inches,1 英尺=2.54厘米)来表示屏幕的大小。 如果知道屏幕的宽度和高度就可以计算出屏幕的大小。如下图所示:
屏幕密度(Screen density)
屏幕密度指的是单位面积上的像素点的个数。像素点(pixel)屏幕上最小的显示区域,不同分辨率的手机上像素点的大小不一样,同样尺寸的屏幕像素点越大屏幕上的像素点总数越少,分辨率越低,像素点越小,屏幕上像素点个数越多,分辨率越高。如下图所示:
DPI
DPI(Dots Per Inch),每一英尺上像素点的个数,dpi是用于衡量屏幕分辨率的尺度,dpi越大屏幕分辨率越高,DPI计算公式:
240x320, 1.5"x2"
上面是一个240320也就是说x轴像素点240pixels,y轴像素点320pixels,屏幕尺寸1.52,物理尺寸长2英尺,宽1.5英尺。 先计算屏幕的大小
240x320, 1.5"x2"
的dpi是160.
基本单位
1、像素(px)
含义:通常所说的像素,就是CCD/CMOS上光电感应元件的数量,一个感光元件经过感光,光电信号转换,A/D转换等步骤以后,在输出的照片上就形成一个点,我们如果把影像放大数倍,会发现这些连续色调其实是由许多色彩相近的小方点所组成,这些小方点就是构成影像的最小单位“像素”(Pixel)。简而言之,像素就是手机屏幕的最小构成单元。 单位:px(pixel),1px = 1像素点 一般情况下UI设计师的设计图会以px作为统一的计量单位。
2、分辨率
含义:手机在横向、纵向上的像素点数总和 一般描述成 宽*高 ,即横向像素点个数 * 纵向像素点个数(如1080 x 1920)。 单位:px(pixel),1px = 1像素点
3、屏幕尺寸(in)
含义:手机对角线的物理尺寸 单位 英寸(inch),一英寸大约2.54cm 常见的尺寸有4.7寸、5寸、5.5寸、6寸
4、屏幕像素密度(dpi)
含义:每英寸的像素点数。 例如每英寸内有160个像素点,则其像素密度为160dpi。 单位:dpi(dots per inch) 计算公式: 像素密度 = 像素 / 尺寸 (dpi = px / in) 标准屏幕像素密度(mdpi): 每英寸长度上还有160个像素点(160dpi),即称为标准屏幕像素密度(mdpi)。
dp与px的换算方法
px=density*dp
因此dp与px的换算方法如下:
px = dp * (dpi / 160)
分辨率和屏幕密度以及像素大小之间的关系如下图:
在Android中直接使用px作单位的问题
如果在布局中直接使用px来做单位会有什么问题,如下图,在图中a的宽度为2px,高度为2px,在左图中由于像素点比右图像素点大,因此作图中的a明显比右图中的a大。
下面来看下在Android中为什么要在不同的分辨率的目录下放大小不同的图:比如在drawable,drawable-hdpi,drawable-xhdpi,drawable-xxhdpi,drawable-xxxhdpi目录下图片大小不一样:
注意在Android中所有的尺寸单位最后都是转化为px后再显示的,因为屏幕显示的基本单位就是px
源码中dp转换px的公式
private float dipToPx(float dip) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
getResources().getDisplayMetrics());
}
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;
}
屏幕适配时遇到问题原因和解决方案
由上面的公式可以看出px和dp的转换与metrics.density
相关,下面看一下源码里面对metrics.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.
*
* <p>This value does not exactly follow the real screen size (as given by
* {@link #xdpi} and {@link #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).
*
* @see #DENSITY_DEFAULT
*/
上面的总结一下就是:标准情况下240x320, 1.5"x2"
的屏幕上density=1,就是说物理尺寸时1.52英寸的情况下,但是当240320屏幕物理尺寸不是1.52的时候此时density还是等于1。由dpi和density的计算公式可知这时的density是有问题的。因为此时240320由于物理尺寸不是1.5*2,算出来的dpi不等于160。
public static final int DENSITY_DEFAULT = 160;
density = DENSITY_DEVICE / (float) DENSITY_DEFAULT;
如果density计算有问题那么dp转换为px就会有问题,所以在有些手机上有时就会出现很奇怪的适配问题,比如字体显示偏小,布局偏小等等,解决这个问题可以使用下面方法:即提供一个计算density的方法不用系统的density计算导致的问题。
public class ScreenUtils {
//Negotiate with the designer to define a design dimension. Here is 1920*1080 resolution set.
private static final float widthdp = 360f;
private static final float heightdp = 640f;
//Recording system settings
private static float systemDensity = 0;
private static float systemScaledDensity = 0;
public void setCustomDensity(@NonNull final Activity activity) {
DisplayMetrics displayMetrics = activity.getApplication().getResources().getDisplayMetrics();
if (systemDensity == 0) {
//Initialization
systemDensity = displayMetrics.density;
systemScaledDensity = displayMetrics.scaledDensity;
//Add a listener. If the user changes the font of the system, the system will return the listener.
activity.getApplication().registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (newConfig != null && newConfig.fontScale > 0) {
systemScaledDensity = activity.getApplication().getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
//Target value calculation => PX = DP * density
final float targetDensity = displayMetrics.widthPixels / widthdp;
final float targetScaledDensity = targetDensity * (systemScaledDensity / systemDensity);
final int targetDensityDpi = (int) (160 * targetDensity);
//Set the calculated value
displayMetrics.density=targetDensity;
displayMetrics.scaledDensity=targetScaledDensity;
displayMetrics.densityDpi=targetDensityDpi;
//Set the value of activity
final DisplayMetrics activityDisplayMetrics =activity .getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.scaledDensity = targetScaledDensity;
activityDisplayMetrics.densityDpi = targetDensityDpi;
}
}
参考文献
1、 stackoverflow.com/questions/2…
2、 developer.android.com/guide/pract…
3、 developer.android.com/training/mu…
4、 developer.android.com/guide/pract…
5、 laaptu.wordpress.com/tag/android…
6、 www.codexiu.cn/android/blo…