Android屏幕适配总结

1,709 阅读8分钟

前言

好久之前就想写一篇跟屏幕适配相关的文章一直没有动笔,这次下决心抽周末的时间结合我在实际项目中所遇到的问题写一篇博客。

Android屏幕组成

Android手机屏幕是由很多的像素点(pixels)组成的,从左到右称为x轴,从上到下为y轴。屏幕的分辨率就是屏幕上x轴上的像素点格式乘以y轴上的像素点个数。320*480的屏幕就是有320个像素点在x轴上,有480个像素点在y轴上。如下图所示:

在这里插入图片描述

Android屏幕大小

什么是屏幕的大小? 对于手机,平板,电脑,或者电视,通常用屏幕的对角线的长度单位为英尺(inches,1 英尺=2.54厘米)来表示屏幕的大小。 如果知道屏幕的宽度和高度就可以计算出屏幕的大小。如下图所示:

在这里插入图片描述
屏幕的宽为1.8,高度为2.6可以计算出屏幕的大小为3.2。

屏幕密度(Screen density)

屏幕密度指的是单位面积上的像素点的个数。像素点(pixel)屏幕上最小的显示区域,不同分辨率的手机上像素点的大小不一样,同样尺寸的屏幕像素点越大屏幕上的像素点总数越少,分辨率越低,像素点越小,屏幕上像素点个数越多,分辨率越高。如下图所示:

在这里插入图片描述
同样大小的屏幕上同样大小的a,左边的像素点大,所以a所包含的像素点就少,分辨率就低,右边像素点小,a包含的像素点数量多所以分辨率更高。下面的图更加清晰的表现了像素点大小和分辨率的关系。
在这里插入图片描述
在面积1inch*1inch的屏幕上像素点越大像素点的总数越少,因此分辨率越低。在Android中用dpi来表示像素点的密度,单位面积上像素点越多屏幕分辩率越高所以dpi称为了衡量一个屏幕好坏的标准之一。

DPI

DPI(Dots Per Inch),每一英尺上像素点的个数,dpi是用于衡量屏幕分辨率的尺度,dpi越大屏幕分辨率越高,DPI计算公式:

在这里插入图片描述
其中x是屏幕x轴的像素点数,y是屏幕y轴的像素点数单位是pixels,s是屏幕的大小单位是pixels,s的计算在屏幕大小里面已经给出了计算方法:
在这里插入图片描述
其中a,b分别指屏幕物理上的宽和高,单位是inch(英尺),下面看下dpi计算的实例:

 240x320, 1.5"x2" 

上面是一个240320也就是说x轴像素点240pixels,y轴像素点320pixels,屏幕尺寸1.52,物理尺寸长2英尺,宽1.5英尺。 先计算屏幕的大小

在这里插入图片描述
再由上面的dpi公式计算dpi
在这里插入图片描述
上面计算得到的值是:160dpi,也就是说

 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大。

在这里插入图片描述
要使在不同分辨率的屏幕上显示图片的大小一样,那么肯定不能用px来做单位,如果使用dp来做单位呢,左右图都是2dp*2dp,此时左右图大小相等,因为1dp在不同的屏幕上大小相同,而1px在不同的屏幕上大小不同。

下面来看下在Android中为什么要在不同的分辨率的目录下放大小不同的图:比如在drawable,drawable-hdpi,drawable-xhdpi,drawable-xxhdpi,drawable-xxxhdpi目录下图片大小不一样:

在这里插入图片描述
为什么在MDPI中图像的大小是XXHDPI中图像大小的1/4呢?原理是这样的,在160dpi中1dp=1px1,而在640dpi中1dp=4px2,也就是说1px1=4px2,也就是说160dpi中一个像素大小是640dpi中一个像素大小的4倍,所以MDPI中的图像看起来比XXXHDPI的图像小4倍,但是由于MDPI中每个像素比XXXHDPI大4倍,所以显示在屏幕上后大小是一样的。

注意在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…

7、 stackoverflow.com/questions/2…

8、tekeye.uk/android/and…

9、programmer.help/blogs/shari…