android 屏幕那點事

轉載自http://www.cnblogs.com/olvo/archive/2012/04/17/2453203.html


 

1.px: 像素, 如分辨率爲240*320, 即爲240px*320px.

2.dp=dip: 如果一個160dpi的屏幕,1dp=1px

3.上邊說的dpi爲dots per inch. 每英寸的點.dots是TM什麼?我理解就是px.

4.sp: 

ppi和dpi經常都會出現混用現象。從技術角度說,“像素”(P)只存在於計算機顯示領域,而“點”(d)只出現於打印或印刷領域。


Android佈局單位及分辨率(dip,dp,sp,px)

  網上介紹Android佈局單位的文章很多,但是我翻了不少,卻發現大部分都是一個拷貝的版本,當然網絡上也有不少是是根據個人使用習慣寫的一些心得,最終經過整合後,walfred將這些很基礎的知識給整合吸收了,所以這裏會結合自己的理解將Android的佈局單位的使用做一個簡單的概括。

單位一覽表

px:單位尺寸裏的像素點

dp:一個基於density的抽象單位,如果一個160dpi的屏幕,1dp=1px

dip:等同於dp

sp:同dp相似,但還會根據用戶的字體大小偏好來縮放,常用來作爲字體大小的單位。

        這些單位我們都見過,但是真正拿到手上用的卻(對walfred而言)用到較多的就是dip(dp)和sp呢,所以我會重點講解這兩個單位的。

像素px與屏幕密度density

        既然知道了像素是單位裏面的像素點,我們就可以通過像素來求得屏幕密度,下面的公式即爲像素與像素密度的換算關係:

pixs =dips * (densityDpi/160)

dips=(pixs*160)/densityDpi

        那我們平時使用的240*320像素的手機(WQVGA/QVGA)、320*480的手機(HVGA)及現在主流的480*800的手機(WVGA),我們都可以算出其屏幕密度,一般我們在計算時計算寬度就可以了,高度同理,所以就得出:

240*320像素的手機(WQVGA/QVGA)的density=120;

320*480的手機(HVGA)的density=160;

480*800的手機(WVGA)density=240;

        1、所以得出結論

        Android的屏幕密度是以160爲基準的,屏幕密度(density)爲160時,是將一英寸分爲160份, 每一份是1像素;同理如果屏幕密度(density)爲240時,是將一英寸分爲240份,,每一份是1像素,所以近來的新的sdk爲了適配不同的屏幕分辨率的機型,已經陸續取消採用像素px作爲佈局單位這主要是針對不同設備而言的。因爲px不管在什麼樣的設備上都是那樣長,但是dip會根據設備變化;

        2、谷歌的策略       

當屏幕density=240時使用hdpi標籤的資源;

當屏幕density=160時,使用mdpi標籤的資源 ;

當屏幕density=120時,使用ldpi標籤的資源

        3、獲取設備像素

        參見文章:獲取手機屏幕分辨率

        4、單位代碼換算

  Java代碼
  1. public static int dip2px(Context context, float dipValue){   
  2.         final float scale = context.getResources().getDisplayMetrics().density;   
  3.         return (int)(dipValue * scale + 0.5f);   
  4. }   
  5.  
  6. public static int px2dip(Context context, float pxValue){   
  7.         final float scale = context.getResource().getDisplayMetrics().density;   
  8.         return (int)(pxValue / scale + 0.5f);   
  9. }   
  10.  
  11. public static int dip2px(Context context, float dipValue){   
  12.         final float scale = context.getResources().getDisplayMetrics().density;   
  13.         return (int)(dipValue * scale + 0.5f);   
  14. }   
  15.  
  16. public static int px2dip(Context context, float pxValue){   
  17.         final float scale = context.getResource().getDisplayMetrics().density;   
  18.         return (int)(pxValue / scale + 0.5f);   
  19. }   

觀念入門:什麼是DPI?

CoverDPI是個很熟悉又陌生的名詞,舉凡印表機、掃瞄器甚至是光學滑鼠都會看到它的蹤影,但是你知道10元硬幣可以用來目測DPI嗎?還有如何透過簡單的試算,知道螢幕解析度、滑鼠DPI、和遊標移動距離的關係?

 

名詞

  • 英:dots per inch

解釋

其實DPI是dots per inch的縮寫,也就是一英吋(2.54公分)的長度中,可容納的點陣數。

如果將掃瞄的影像或印刷出來的紙張放大到一定的極限,能發現影像其實是由許多的點構成。在短短的一英吋長度中,如果可容納300個點,解析度就是300dpi。同理可證,若有印表機解析度能到達600dpi,就代表它的影像品質是300dpi的兩倍,因爲在同樣的長度中,能容納600個墨點,影像細節自然也就更豐富。由此可知,在一張4×6英吋的相片紙上,300dpi與600dpi印表機印出的相片墨點分別是1200×1800與2400×3600,呈現出的影像當然是600dpi比較漂亮。

不過人眼可辨識的影像細節還是有極限,DPI當選購的參考就好,不需要過度追求高解像能力。

▲ 一英吋就是十元硬幣直徑

對一英吋沒概念沒關係,拿出十元硬幣比比看就知道,十元硬幣直徑的長度上有多少點,DPI就是多少。

應用

光學滑鼠也很常用DPI這個規格,主要是標榜光學或雷射頭的解像能力。假設螢幕解析度是800×600,滑鼠的DPI是800,當遊標在桌面最右邊時,你只需要把滑鼠左移一英吋,遊標就會從最右邊跑到最左邊。

再把滑鼠的DPI提升到1600,這時把遊標從右端移到左端,只需要把滑鼠移動0.5英吋。當滑鼠DPI值越高,在同樣解析度的螢幕下,移動速度更快也更流暢。

▲ 滑鼠可達4000dpi

部分遊戲滑鼠可顯示DPI,目前最高可達4000dpi。一般來說800dpi的滑鼠就很實用,太高反而會不好操控。

換個角度想,當螢幕從15吋(解析度1024×768)升級到24吋(解析度1920×1200),滑鼠一樣是400dpi。15吋的移動時只要2.56英吋(6.5公分),24吋卻要4.8英吋(12.1公分)。

要移動超過12公分才能把遊標從螢幕一端移到另一端,對電腦操作上實在太過吃力,因此升級高解析度螢幕的同時,最好能配上適當的滑鼠用起來纔夠順手。

▲ 顆粒感重?檢查相片和印表機

有些相片印出來顆粒感重,除了本身解析度不夠外,也可能是列印品質太差,相片列印前要確定解析度可達300dpi以上。

 


Android手機屏幕那點事

首先,一塊屏幕有幾個參數,屏幕的物理尺寸,分辨率,像素密度(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值的過程)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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,通過代碼:

1
2
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轉換單位的函數裏面這段代碼:

1
2
3
4
5
6
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圖片。這裏就不詳細說了。


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