首先,一塊屏幕有幾個參數,屏幕的物理尺寸,分辨率,像素密度(Density, DPI)。

其中

  • 物理尺寸,就是所說的幾寸的屏幕,代表屏幕對角線的長度,比如3.5寸、3.7寸、4寸、7寸等。
  • 分辨率,是屏幕總共能顯示的像素數,通常我們都說幾百×幾百,比如240*320,320*480,480*800等,我們一般直接說乘后的結果。
  • 像素密度(DPI),DPI的全稱是dots per inch,每英寸點數,還有個詞是PPI,是每英寸像素數,其實PPI感覺更准確一些,這兩個比較糾結,很多時候混用,這里就不明確區分了。(本文的意思都是“每英寸像素數”)

這三個參數,任兩個確定后,第三個量就是確定了。公式為:分辨率(總像素數)= 物理尺寸 × 像素密度

    解釋一下: 像素密度就是每物理尺寸像素數, 一乘當然左邊=右邊,都是像素數嘛!

  • 比如一個3寸的屏幕,分辨率為240×320,那么密度為 開方(240^2+320^2)/3 約等於為133。
  • 再比如一個3.5寸的屏幕,分辨率為320x480,那么密度為 開方(320^2+480^2) / 3.5 約等於165.
  • 再比如一個3.5寸的屏幕,分辨率為480×800,那么密度為 開方(480^2+800^2)/3.5 約等於為194。
  • 在比如一個3.5寸的屏幕,分辨率為960x640,那么密度為 開方(960^2+640^2)/3.5 約等於329。
  • 再比如一個4寸的屏幕,分辨率為480x800,那么密度為 開方(480^2+800^2)/4 約等於233。

面對種類旁雜的屏幕,開發人員該怎么辦,人工針對不同屏幕做相應調整,No!

讓機器調整!開發人員是天生懶惰的!

那么要調整什么,目的該是讓界面元素的物理大小在所有設備上保持一致(但是屏大的似乎天然可以顯示的大一點,小屏的可以小一點。)

過去,開發人員通常以像素為單位設計計算機用戶界面。例如,定義一個寬度為300像素的表單字段,列之間的間距為5個像素,圖標大小為16×16像素等。這樣處理的問題在於,如果在一個每英寸點數(dpi)更高的新顯示器上運行該程序,則用戶界面會顯得很小。在有些情況下,用戶界面可能會小到難以看清內容。

針對屏幕的三個參數,分析如下:

  • 同樣物理尺寸,分辨率不同,那么如果按照像素設計,就會產生,分辨率大的那個,圖像很小.物理尺寸就會很小.
  • 同樣分辨率,不同物理尺寸,如果按鈕找像素設計,實際看起來的物理比例是一樣的.
  • 看起來物理尺寸一樣,不同分辨率,分辨率大的,屏幕尺寸就要大.
  • 看起來物理尺寸一樣,不同屏幕尺寸,大尺寸的,就要像素多.

那么Android框架為自動調整尺寸做了什么呢?

就是密度無關像素,原文如下

The density-independent pixel is equivalent to one physical pixel on a 160 dpi screen.

是說,以160dpi為標准,在一個160dpi的屏幕上的1個物理像素作為設備無關像素的1個像素,也就是Android最佳實踐中推薦的dip/dp(以下這兩個單位表示同樣含義,dip常見於Google官方示例中)這個單位。

針對於字體,Android設計了sp這個單位,這個於dp的不同在於,字體大小在dp的基礎上,可以根據用戶的偏好設置,相應調整字體大小,所以是scale的。

但是!

Android的做法不是根據160dpi這個標准值和設備實際的dpi的比值進行縮放!而是設定了一套標准化尺寸和密度:

  • 標准化物理尺寸: small, normal, large, and xlarge
  • 標准化屏幕密度: ldpi (low), mdpi (medium), hdpi (high), and xhdpi (extra high)

Each generalized size or density spans a range of actual screen sizes or density. For example, two devices that both report a screen size of normal might have actual screen sizes and aspect ratios that are slightly different when measured by hand. Similarly, two devices that report a screen density of hdpi might have real pixel densities that are slightly different. Android makes these differences abstract to applications, so you can provide UI designed for the generalized sizes and densities and let the system handle any final adjustments as necessary. Figure 1 illustrates how different sizes and densities are roughly categorized into the different size and density groups.(摘自官方文檔)

如圖: 

screens-ranges

(我曾經以為,Android會根據實際dpi進行縮放,這也是我迷惑很久,之前寫就在這個卡住了)

為了證明Android確實不是不是根據實際dpi進行縮放,我查閱了相關的源代碼。

我們知道當顯卡繪制一個圖像的時候,是根據物理像素繪制的。所以,當開發人員設定dp這種單位的時候,需要一個轉化過程,將sp轉化為px。

如果按我之前所想,計算公式該是:實際dpi / mdpi(也就是160dpi)然后乘上sp的數值,這樣就得到了在不同設備上物理大小完全一樣的的界面元素。

但是Android不是這樣設計的,正如前文所說,是根據那套標准化的密度來進行轉換的。通過如下代碼(這個是Android將dp轉化為px值的過程)。

    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;
    }

可以看到,如果單位是dip(dp),那么返回值則是dip的value * metrics.density。

這里的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.
This value does not exactly follow the real screen size (as given by xdpi and 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).
(摘自Google官方文檔,懶得翻譯了,當然也是怕翻譯壞了原來的味道,這段還是相當重要的)

重點是This value does not exactly follow the real screen size。這也解釋我之前的疑惑。

這個density值Displaymetrics記錄的,如果你想看看實際情況,可以獲取Displaymetrics,通過代碼:

DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);

然后就能得到metrics的值。

另外!

這類還有xdpi和ydpi這兩個值,官方文檔上說:The exact physical pixels per inch of the screen in the X(Y) dimension.

然而,當我試圖獲取某些機器的這兩個值的時候卻與我手動計算所得到的值完全不同!

后來翻閱StackOverflow,看到也有人遇到類似問題,

作者獲得了幾個設備的dip值,如下:

  • HTC Desire Z: 480x800, density : HIGH, xdpi: 254.0, ydpi: 254.0
  • Motorola Defy: 480x854, density : HIGH, xdpi: 96.0, ydpi: 96.0
  • Samsung Galaxy S2: 480x800, density : HIGH, xdpi: 217.71428, ydpi: 218.49463
  • LG Optimus 2X: 480x800, density : HIGH, xdpi: 160.0, ydpi: 160.0

(原文地址: http://stackoverflow.com/questions/6224900/android-incorrect-size-for-views-with-mm-or-inch-dimensions

可以看到對於Moto和LG的dpi是明顯錯誤的。

再回想剛才Android轉換單位的函數里面這段代碼:

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);

對於這幾個單位的處理都用到了xdpi,所以很可能轉換后是錯誤的值,
(這里應該仍然算是個疑問,難道真的沒有辦法得到正確的值嗎?我們都知道是不推薦用pt,in,mm這種單位的,這是否也是一個方面)

至此關於屏幕的問題大體說完,然后就是提供的資源問題,當我們設置了一個界面元素的的大小后,對於不是標准dpi的機器上就要進行縮放,那么對於繪制的矢量元素,自然是不用管,而對於圖像這種位圖,縮放后會導致模糊等問題,所以就要對標准化dpi的幾個大小,提供相應的替換版本,Android會根據實際屏幕規格,進行相應替換,並且有相應的查找資源的規則,看Android源碼,可以知道,Android的框架的默認ui使用了大量nine-patch圖片。這里就不詳細說了。

好吧,這次就到這里了。