Android屏幕適配總結

前言

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

Android屏幕組成

Android手機屏幕是由不少的像素點(pixels)組成的,從左到右稱爲x軸,從上到下爲y軸。屏幕的分辨率就是屏幕上x軸上的像素點格式乘以y軸上的像素點個數。320*480的屏幕就是有320個像素點在x軸上,有480個像素點在y軸上。以下圖所示:java

在這裏插入圖片描述

Android屏幕大小

什麼是屏幕的大小? 對於手機,平板,電腦,或者電視,一般用屏幕的對角線的長度單位爲英尺(inches,1 英尺=2.54釐米)來表示屏幕的大小。 若是知道屏幕的寬度和高度就能夠計算出屏幕的大小。以下圖所示: android

在這裏插入圖片描述
屏幕的寬爲1.8,高度爲2.6能夠計算出屏幕的大小爲3.2。

屏幕密度(Screen density)

屏幕密度指的是單位面積上的像素點的個數。像素點(pixel)屏幕上最小的顯示區域,不一樣分辨率的手機上像素點的大小不同,一樣尺寸的屏幕像素點越大屏幕上的像素點總數越少,分辨率越低,像素點越小,屏幕上像素點個數越多,分辨率越高。以下圖所示: bash

在這裏插入圖片描述
一樣大小的屏幕上一樣大小的a,左邊的像素點大,因此a所包含的像素點就少,分辨率就低,右邊像素點小,a包含的像素點數量多因此分辨率更高。下面的圖更加清晰的表現了像素點大小和分辨率的關係。
在這裏插入圖片描述
在面積1inch*1inch的屏幕上像素點越大像素點的總數越少,所以分辨率越低。在Android中用dpi來表示像素點的密度,單位面積上像素點越多屏幕分辯率越高因此dpi稱爲了衡量一個屏幕好壞的標準之一。

DPI

DPI(Dots Per Inch),每一英尺上像素點的個數,dpi是用於衡量屏幕分辨率的尺度,dpi越大屏幕分辨率越高,DPI計算公式:app

在這裏插入圖片描述
其中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英尺。 先計算屏幕的大小 ide

在這裏插入圖片描述
再由上面的dpi公式計算dpi
在這裏插入圖片描述
上面計算獲得的值是:160dpi,也就是說

240x320, 1.5"x2" 
複製代碼

的dpi是160.wordpress

基本單位

一、像素(px)佈局

含義:一般所說的像素,就是CCD/CMOS上光電感應元件的數量,一個感光元件通過感光,光電信號轉換,A/D轉換等步驟之後,在輸出的照片上就造成一個點,咱們若是把影像放大數倍,會發現這些連續色調實際上是由許多色彩相近的小方點所組成,這些小方點就是構成影像的最小單位「像素」(Pixel)。簡而言之,像素就是手機屏幕的最小構成單元。 單位:px(pixel),1px = 1像素點 通常狀況下UI設計師的設計圖會以px做爲統一的計量單位。字體

二、分辨率ui

含義:手機在橫向、縱向上的像素點數總和 通常描述成 寬*高 ,即橫向像素點個數 * 縱向像素點個數(如1080 x 1920)。 單位:px(pixel),1px = 1像素點

三、屏幕尺寸(in)

含義:手機對角線的物理尺寸 單位 英寸(inch),一英寸大約2.54cm 常見的尺寸有4.7寸、5寸、5.5寸、6寸

四、屏幕像素密度(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;
    }

}
複製代碼

參考文獻

一、 stackoverflow.com/questions/2…

二、 developer.android.com/guide/pract…

三、 developer.android.com/training/mu…

四、 developer.android.com/guide/pract…

五、 laaptu.wordpress.com/tag/android…

六、 www.codexiu.cn/android/blo…

七、 stackoverflow.com/questions/2…

八、tekeye.uk/android/and…

九、programmer.help/blogs/shari…

相關文章
相關標籤/搜索