Android屏幕適配總結

前言

好久之前就想寫一篇跟屏幕適配相關的文章一直沒有動筆,這次下決心抽週末的時間結合我在實際項目中所遇到的問題寫一篇博客。

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、 https://stackoverflow.com/questions/2025282/what-is-the-difference-between-px-dip-dp-and-sp
2、 https://developer.android.com/guide/practices/screens_support.html
3、 https://developer.android.com/training/multiscreen/screendensities.html#TaskUseDP
4、 https://developer.android.com/guide/practices/screens-distribution
5、 https://laaptu.wordpress.com/tag/android-dp-to-px-conversion/
6、 http://www.codexiu.cn/android/blog/8603/
7、 https://stackoverflow.com/questions/22141309/whats-android-dpi
8、https://tekeye.uk/android/android-dpi-dip-dp-ppi-sp-and-screens
9、https://programmer.help/blogs/sharing-and-summarizing-android-adaptation-problems.html

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