本文記錄一些適配問題的研究,基礎概念不做過多介紹。
Android在做屏幕適配的時候一般考慮兩個因素:分辨率和dpi。分辨率是屏幕在橫向、縱向上的像素點數總和,一般用“寬x高”的形式表示,例如:1080x1920。dpi是dots per ich的縮寫,表示每英寸的像素點數,例如160dpi指手機水平或垂直方向上每英寸距離有160個像素點。
一、dp和px
dp和px都是編寫布局時的單位,它們之間可以通過dpi來換算,換算公式如下:
- 公式一:px值 = dp值 * (dpi/160)
常見的還有一個density的概念,表示基準比例,density = (dpi/160),所以還有如下公式:
- 公式二:px值 = dp值 * density
注:公式中的160是android中規定的基準,即:在160dpi的屏幕上,1dp=1px
通過上面的公式理解dp的概念
dp和dip的含義相同,都是density-independent pixel的縮寫,表示密度無關像素,可以保證在不同像素密度的屏幕上顯示相同的效果。舉個例子:
兩種常見的屏幕參數如下:
- 屏幕1:分辨率=720x1280,dpi=320
- 屏幕2:分辨率=1080x1920,dpi=480
如果要實現一個view的寬度占屏幕1寬度的一半,由分辨率可知需要view的寬度是360px,根據公式一可以知道使用180dp可以實現相同的效果(360 = dp值 * (320/160) -> dp值=180)。
接下來再根據公式一,看看180dp在屏幕2上的顯示效果,px值 = 180 * (480/160) = 540,正好是屏幕2寬度的一半。所以,使用180dp就可以同時在兩個屏幕上顯示相同的效果了。
二、實際工作中如何使用dp適配
實際工作中,我們一般會使用限定符建立多個資源文件來做適配,一般會適配mdpi,hdpi,xhdpi,xxhdpi等,比如res下的values資源目錄如下:
- res/values-mdpi
- res/values-hdpi
- res/values-xhdpi
然后在不同的目錄下定義不同的dp值。上面180dp的例子說明了dp值已經保證了在不同像素密度的屏幕上顯示相同的效果,那么為什么還要針對不同的屏幕定義不同的dp值?不同的dp值如何確定的?下面結合例子解釋這兩個問題。
1. 為什么要針對不同的屏幕定義不同的dp值?
這是因為android機型屏幕尺寸太碎片化了,一個dp值并不能滿足所有機型,比如常見的還有如下這種:
- 屏幕3:分辨率=480x800,dpi=240
通過前面的公式計算可以知道,前面例子的180dp在這個屏幕上并不是寬度的一半,160dp才是一半。由此可見,一個dp值并不能滿足所有屏幕,所以需要使用限定符適配不同的dpi。最終適配這三種屏幕如下:
- res/values-hdpi/dimens.xml 中定義資源160dp 適配屏幕3
- res/values-xhdpi/dimens.xml 中定義資源180dp 適配屏幕1
- res/values-xxhdpi/dimens.xml 中定義資源180dp 適配屏幕2
限定符mdpi,hdpi,xhdpi等與dpi的對應關系后面給出
2. 不同限定符下的dp值如何確定的?
比如現在項目中有了一套屏幕1(xhdpi)的資源res/values-xhdpi/dimens.xml
,其中有一個資源值是90dp,如果要求再適配一下屏幕3(hdpi),那么res/values-hdpi/dimens.xml中與90dp同名的資源應該是多少dp呢?通過前面的介紹,這個值是很好計算的:
- 根據公式一計算90dp在屏幕1上是多少px
px = 90 * (320/160) = 180 - 根據1中計算出的像素,計算在屏幕3下同樣比例的像素數
屏幕3下的px = 480 * (180/720) = 120 - 根據上一步的結果和公式一計算出在屏幕3的dp值
dp = 80
如果把計算過程中的90dp改成任意值r,那么最終,屏幕1的每一個資源值 r 乘以 8/9 就是在屏幕3下的dp值,對應的資源文件就是:
- res/values-xhdpi/dimens.xml 中定義資源 r dp (適配屏幕1)
- res/values-hdpi/dimens.xml 中定義資源 r*8/9 dp (適配屏幕3)
這里計算出來的8/9是使用橫向分辨率和dpi計算出來的,如果是縱向的話并不是這個比例,所以適配的時候需要區分橫豎向不同的比例轉換。比如寬、橫向邊距等使用橫向的比例,高、豎直邊距等使用豎向比例。
需要注意的是這里是按比例計算出來的,最終的值還得根據實際情況和顯示效果而定。因為不一定所有界面的設計都是按比例適配的;還有就是有些帶虛擬按鍵的1080x1920的手機,真實的豎直像素數應該是1920減去虛擬按鍵的高度;還有一點,在android開發中所說的dpi的值并不是物理定義的,而是系統文件寫進去的,所以這個值是可以被修改的。另一層意思是,dpi并不是由分辨率和屏幕尺寸計算出來的固定值。比如當前常見的一種機型分辨率是1080x1920,尺寸是5.15英寸,dpi是480。按照dpi的定義,使用這個分辨率和尺寸計算dpi的話,結果并不是480。所以對分辨率和尺寸都相同的手機,dpi值不一定相同,完全看手機廠商如何定義。不過,為了使顯示效果最好,一般比較標準的手機dpi和分辨率都和下表一致:
ldpi | mdpi | hdpi | xhdpi | xxhdpi | |
---|---|---|---|---|---|
分辨率 | 240x320 | 320x480 | 480x800 | 720x1280 | 1080x1920 |
dpi | 120 | 160 | 240 | 320 | 480 |
所以,不同限定符下的dp值,使用上面1,2,3步的計算方法能滿足大部分主流機型,但不一定能完美適配所有機型。
限定符與dpi的具體對應關系如下:
限定符 | ldpi | mdpi | hdpi | xhdpi | xxhdpi |
---|---|---|---|---|---|
dpi | dpi<=120 | 120<dpi<=160 | 160<dpi<=240 | 240<dpi<=320 | 320<dpi<=480 |
限定符的知識不僅如此,這里不做過多介紹。
三、DisplayMetrics類和wm命令
代碼中,可以通過DisplayMetrics類來獲取屏幕的一些信息,有三種方式可以獲取DisplayMetrics的實例:
//方法1
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
//方法2
WindowManager wm = (WindowManager) getSystemService(
Context.WINDOW_SERVICE);
DisplayMetrics metrics= new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(metrics);
//方法3
DisplayMetrics metrics = getResources().getDisplayMetrics();
可以通過DisplayMetrics獲取的如下信息:
//等號后面的數值是某個手機的參數,分辨率=1080x1920,dpi=480,尺寸5.15英寸
metrics.heightPixels = 1920
metrics.widthPixels = 1080
metrics.densityDpi = 480 //dpi值
metrics.density = 3.0 //基準比例,dpi/160
metrics.xdpi = 422.03 //x方向準確的物理像素密度
metrics.ydpi = 424.069 //y方向準確的物理像素密度
metrics.scaledDensity = 3.0
這里的xdpi和ydpi和densityDpi的值不同,再次說明android開發中所說的dpi的值不是由硬件決定的。
利用DisplayMetrics類,可以實現一些常用的工具方法,比如dp轉px,px轉dp等。或者在一些不方便使用資源適配的情況下,可以通過這個類判斷不同的dpi來通過java代碼來適配。
wm命令
wm命令是高通平臺下對手機分辨率、像素密度等進行設置的命令。用法很簡單:
首先使用adb shell命令進入手機的shell中,然后就可以使用wm命令了,常用的命令如下:
wm size //輸出手機的分辨率信息
如果要修改手機分辨率,上面的命令加上分辨率參數即可,比如把手機分辨率修改為800x1280,命令如下
wm size 800x1280
如果要把上面命令修改的分辨率還原為手機原始的分辨率,使用下面的命令
wm size reset
上面是分辨率相關的命令,dpi的命令和上面的類似,如下:
wm density
wm density 240
wm density reset
掌握wm命令后,就可以方便的查看手機分辨率和dpi了,也可以使用同一部手機測試多種分辨率的適配。
注:使用wm命令修改分辨率或dpi后,在某些手機中,再使用reset命令還原后,手機某些內容可能會顯示不正常(比如狀態欄、輸入法等),重啟手機即可解決。
四、小結
通過前面幾節,應該知道以下幾點:
- 屏幕適配的時候一般考慮的兩個因素:分辨率和dpi。
- 利用dpi換算dp和px的值(兩個公式)
- 使用限定符適配不同dpi的屏幕,不同限定符下dp值的計算
- android開發中使用的dpi的值是不固定的,可以修改的
- 獲取手機屏幕信息的DisplayMetrics類和修改屏幕參數的wm命令
五、drawable下的圖片適配
這里介紹drawable下的圖片資源的讀取和縮放的規則,也可以這樣說,如何使用一套圖片資源適配多種dpi。郭神的一篇博客 Android drawable微技巧,你所不知道的drawable的那些細節講的很清晰,這里取其精華做一個總結。
圖片資源的讀取規則
當我們使用資源id來去引用一張圖片時,Android會使用一些規則來幫我們匹配最適合的圖片。什么叫最適合的圖片?比如我的手機屏幕密度是xxhdpi,那么drawable-xxhdpi文件夾下的圖片就是最適合的圖片。因此,當我引用一張圖片時,如果drawable-xxhdpi文件夾下有這張圖就會優先被使用,在這種情況下,圖片是不會被縮放的。但是,如果drawable-xxhdpi文件夾下沒有這張圖時, 系統就會自動去其它文件夾下找這張圖了,優先會去更高密度的文件夾下找這張圖片,也就是drawable-xxxhdpi文件夾,然后發現這里也沒有android_logo這張圖,接下來會嘗試再找更高密度的文件夾,發現沒有更高密度的了,這個時候會去drawable-nodpi文件夾找這張圖,發現也沒有,那么就會去更低密度的文件夾下面找,依次是drawable-xhdpi -> drawable-hdpi -> drawable-mdpi -> drawable-ldpi。
android項目資源下的drawable(不帶任何限定符)目錄默認就是drawable-mdpi的意思
作者:郭霖
鏈接:http://blog.csdn.net/guolin_blog/article/details/50727753
圖片資源的縮放規則
根據上面的讀取規則,如果最終沒有讀取到最適合的圖片,而是讀取了低密度或高密度的圖片,那么系統會自動做一個縮放操作(低密度的圖片放大,高密度的圖片縮小)。具體的縮放比例就是dpi的比例:
ldpi | mdpi | hdpi | xhdpi | xxhdpi | |
---|---|---|---|---|---|
dpi | 120 | 160 | 240 | 320 | 480 |
比例 | 3 | 4 | 6 | 8 | 12 |
比如當前手機dpi是160(對應mdpi)
如果讀取了drawable-mdpi下的圖片大小是48x48,那么顯示到屏幕上的圖片大小是48x48(最適合的圖片不縮放)
如果讀取了drawable-ldpi下的圖片大小是48x48,那么顯示到屏幕上的圖片大小是36x36(48/36 = 4/3)
如果讀取的是drawable-xhdpi下的圖大小是48x48,那么顯示到屏幕上的圖片大小是96x96。
一套圖片資源適配多種dpi
根據Android的開發建議,我們在準備圖片資源時盡量應該給每種密度的設備都準備一套,這樣程序的適配性就可以達到最好,比如AndroidStudio中新建項目的時候,AS會默認給我們生成不同規格的ic_launcher.png作為默認的app圖標,如下:
- mipmap-mdpi/ic_launcher.png (48x48)
- mipmap-hdpi/ic_launcher.png (72x72)
- mipmap-xhdpi/ic_launcher.png (96x96)
- mipmap-xxhdpi/ic_launcher.png (144x144)
括號中是圖片大小,mipmap看成drawable即可。
上面的4個資源,通過計算可知,完全符合前面的圖片資源的縮放規則的比例關系,所以,上面的資源只保留一個的話同樣可以適配另外3種dpi。
類似的,項目中,UI設計師只需要給我們提供一種dpi下的一套圖片即可適配所有dpi,原理就是圖片資源的讀取縮放規則。那么,我們希望UI給我們哪種dpi的圖片呢?當然是高dpi下的圖片了,因為高dpi目錄下的圖片,顯示到低dpi的設備下,由縮放規則可以知道圖片會被縮小。相反的,低dpi圖片顯示到高dpi的設備上,圖片會被放大。圖片縮小幾乎沒有什么副作用,而放大可能會影響圖片質量,所以,使用高dpi目錄下的圖片適配效果更好,比如ic_launcher.png只保留一個的話,選擇保留 mipmap-xxhdpi/ic_launcher.png (144x144)。當然,也不是越高越好,比如當前手機市場有更高密度的手機xxxhdpi,但那是極少數,為了這極少數增加軟件包的大小是不劃算的。當然如果以后xxxhdpi的手機普及了,針對這種級別的屏幕密度來設計圖片就是首選了。
其他問題
- 內存方面,使用xxhdpi規格的圖片適配和使用mdpi規格的圖片適配,理論上它們對內存的占用最終是相同的。比如上面ic_launcher.png的例子,如果選擇使用mdpi適配,那么原圖大小是48x48,顯示到xxhdpi設備上之后,圖片被放大到144x144。如果使用xxhdpi適配,那么原圖大小就是144x144,顯示到xxhdpi設備上不會縮放,所以兩種適配方法,最終顯示到xxhdpi設備上的大小是一樣的,占用內存也一樣。
- 需要注意的是,使用一套圖片做適配,在某些情況下可能會有一些問題,看:Android 開發中 drawable 有必要放多套分辨率的圖片資源嗎?
- wm命令對于測試圖片并不好用,比如480dpi(對應xxhdpi)的手機,如果使用wm命令將手機dpi改為160(對應mdpi),那么這時候依然會優先讀取xxhdpi下的圖片資源,我試了兩個不同廠商的手機都是這樣。
參考文章:
Android中px dpi dip density densityDpi 的相關說明
DPI、PPI、DP、PX 的詳細計算方法及算法來源是什么?
ANDROID 屏幕適配
Android 適配時資源限定符的說明
Android drawable微技巧,你所不知道的drawable的那些細節