Android開發中的各種單位的解釋
Px (Pixel像素)
也稱為圖像元素,是作為圖像構成的基本單元,單個像素的大小并不固定,跟隨屏幕大小和像素數量的關系變化(屏幕越大,像素越低,單個像素越大,反之亦然)。所以在使用像素作為設計單位時,在不同的設備上可能會有縮放或拉伸的情況。Resolution(分辨率)
是指屏幕的垂直和水平方向的像素數量,如果分辨率是 1920*1080 ,那就是垂直方向有 1920 個像素,水平方向有 1080 個像素。Dpi(像素密度)
是指屏幕上每英寸(1英寸 = 2.54 厘米)距離中有多少個像素點。如果屏幕為 320*240,屏幕長 2 英寸寬 1.5 英寸,Dpi = 320 / 2 = 240 / 1.5 = 160。Density(密度)
這個是指屏幕上每平方英寸(2.54 ^ 2 平方厘米)中含有的像素點數量。Dip / dp (設備獨立像素)
也可以叫做dp,長度單位,同一個單位在不同的設備上有不同的顯示效果,具體效果根據設備的密度有關,詳細的公式請看下面 。
計算規則
我們以一個 4.95 英寸 1920 * 1080 的 nexus5 手機設備為例:
Dpi :
- 計算直角邊像素數量: 19202+10802=2202^2(勾股定理)。
- 計算 DPI:2202 / 4.95 = 445。
- 得到這個設備的 DPI 為 445 (每英寸的距離中有 445 個像素)。
Density
上面得到每英寸中有 440 像素,那么 density 為每平方英寸中的像素數量,應該為: 445^2=198025。
Dip
- 先明白一個概念,所有顯示到屏幕上的圖像都是以 px 為單位。
- Dip 是我們開發中使用的長度單位,最后他也需要轉換成 px。
- 計算這個設備上 1dip 等于多少 px:
px = dip x dpi /160
px = 1 x 445 / 160 = 2.78 - 通過上面的計算可以看出在此設備上 1dip = 2.78px,那么這是一個真實的故事嗎? nonono,其中的關鍵值 dpi 并不是我們算出來的 445 ,請往下看。
Android 系統定義的 Dpi
上面計算的 445Dpi 是在 4.95 英寸下的 1920*1080 手機,那如果是 4.75 分辨率下的呢? 4.55 分辨率下的呢?。。。??梢娛呛苈闊┑模粋€分辨率在不同的屏幕尺寸上 Dpi 也不相同。為了解決這個問題, Android 中內置了幾個默認的 Dpi ,在特定的分辨率下自動調用,也可以手動在配置文件中修改。
ldpi | mdpi | hdpi | xhdpi | xxhdpi | |
---|---|---|---|---|---|
分辨率 | 240x320 | 320x480 | 480x800 | 720x1280 | 1080x1920 |
系統dpi | 120 | 160 | 240 | 320 | 480 |
基準比例 | 0.75 | 1 | 1.5 | 2 | 3 |
這是內置的 Dpi ,啥意思? 在 1920*1080 分辨率的手機上 默認就使用 480 的 dpi ,不管的你的尺寸是多大都是這樣,除非廠家手動修改了配置文件,這個我們后面再說。
我們親自嘗試一下:
<TextView
android:id="@+id/tv"
android:layout_width="200dp"
android:layout_height="100dp"
android:text="Hello World!" />
這是一個 textview,高為 200dp 寬為 100dp 。按照我們之前的公式手動計算:
height = 100 x 445 / 160 = 278.5px
width = 200 x 445 / 160 = 556.25px
我們用下列代碼獲取到控件的實際像素看看是否相同:
layout = (RelativeLayout)findViewById(R.id.la);
//要在控件繪制完成后才能獲取到相關信息,所以這里要監聽繪制狀態
layout.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
Log.d("hehehe", textView.getHeight() + "/" + textView.getWidth());
return true;
}
});
輸出的結果為:300/600
內部計算過程為:
height = 100 x 480 / 160 = 300px
width = 200 x 480 / 160 = 600px
其中的 160 是基準值不會變的, 100 和200 是我們設置的 dp ,那么這 480 是從何而來的?說好的 445 呢?
找到我們手機中的 /system/build.prop 文件,其中有一行是這樣:
ro.sf.lcd_density=480
這就指定了這個機型使用的dpi是多少,還有一種情況是沒有這一行(我在模擬器中發現過),那么應該是根據表格中的分辨率來自動設置。
我更改這行為:
ro.sf.lcd_density=320
再次運行上面的測試代碼,輸出結果為:200/400
內部計算過程為:
height = 100 x 320 / 160 = 200px
width = 200 x 320 / 160 = 400px
說到底,因為有dpi這個動態的系數,我們在使用dp的時候才能兼容不同分辨率的設備。
到這里,應該都明白了 dpi 的由來,以及系統 dpi 跟物理 dpi 并不一定相同。在系統中使用的全部都是系統 dpi,沒有使用物理 dpi,也獲取不到物理 dpi。物理 dpi 主要用于廠家對于手機的參數描述(也可以看做 ppi )!
然后。。表格中還有一個東西叫做基準比例,這個其實就是計算 dp -> px 中重要的系數,以 160 為基準,其他的除以 160 得到比例,我們這樣看:
height = 100 x 480 / 160 = 300px
width = 200 x 480 / 160 = 600px
其中的480/160其實就是在求基準比例,這里得到3。如果在熟悉上表的情況下看到機型的分辨率,在設置dp的時候可以直接心算出相對應的px,心算過程如下:
分辨率:1080x1920 -> 系統 DPI:480 -> 基準比例:480 / 160 = 3 -> 對應px:100 x 3 = 300
分辨率:720x1280 -> 系統 DPI:320 -> 基準比例:320 / 160 = 2 -> 對應px:100 x 2 = 200
分辨率:480x800 -> 系統 DPI:240 -> 基準比例:240 / 160 = 1.5 -> 對應px:100 x 1.5 = 150
分辨率:320x480 -> 系統 DPI:160 -> 基準比例:160 / 160 = 1 -> 對應px:100 x 1 = 100
分辨率:240x320 -> 系統 DPI:120 -> 基準比例:120 / 160 = 0.75 -> 對應px:100 x 0.75 = 75
...................
總結:
1. dpi(每英寸像素數)是有預設值的!120-160-240-320-480。對應不同的分辨率。
2. 基準比例 = dpi(每英寸像素數) / 160
3. px = dp x 基準比例
從代碼中獲取相關數值
我們主要使用的類是:DisplayMetrics
以下為官方api說明
A structure describing general information about a display, such as its size, density, and font scaling.
To access the DisplayMetrics members, initialize an object like this:DisplayMetrics metrics = newDisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
這是一個獲取屏幕信息的類,比如大小,密度等。以及初始化的方法。
實際運用如下:
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
//通常我們在使用DisplayMetrics時,都是直接獲取內部變量來使用。所以下面直接列出各個內部變量。
dm.ydpi; //得到物理屏幕上 Y 軸方向每英寸的像素
dm.xdpi; //得到物理屏幕上 X 軸方向每英寸的像素
//ps: 其實這兩個大多數情況下都是相同的
//你能想象上面像素密度大很清晰 下面密度小跟馬賽克一樣嗎 233333
dm.density; //獲取當前設備的基準比例
dm.densityDpi; //獲取系統dpi,隨著 build.prop 文件中的代碼而改變。
dm.widthPixels; //獲取屏幕寬度的像素數量
//獲取屏幕高度的像素數量!
//注意 - 因為這里會自動減去32dp的像素數量,根據分辨率不同的設備,減去的像素數量也不同,但是可以根據公式推算完整(px = dp x 基準比例)。
/*為啥不用dm.densityDpi / 160 得到基準比例?
因為那個會隨著build.prop文件代碼變更而更改,算出來的不一定準確*/
dm.heightPixels + 32 * dm.ydpi / 160;