話說前段時間我寫了一篇drawable微技術的文章,也是引起了不小的反響,因為講的東西是大家天天在使用,但卻偏偏卻沒有深入了解的知識。而本篇文章的投稿者 王月半子 表示「不服」,看完我寫的那篇之後自己也忍不住寫了一篇,我閱讀之後發現寫的挺棒,於是邀請他來公眾號上投稿。由於本篇文章內容較長,因此分為上下兩篇發表。
王月半子的博客地址:http://blog.csdn.net/wrg_20100512
Android適配的問題太多,有屏幕尺寸的適配、屏幕解析度的適配以及不同系統版本的適配。反映在代碼上,就是需要在資源文件上下功夫,主要是layout和drawable目錄下的文件,這裡主要研究一下drawable的適配。首先我們先熟悉一下基本的概念。
Android中的長度單位
px(pixel)
表示屏幕實際的像素。例如,1200×1920的屏幕在橫向有1200個像素,在縱向有1920個像素。
dpi(dot per inch)
表示屏幕密度是指每英寸上的像素點數。Android將根據不同的dpi將Android設備分成多個顯示級別。具體如下:
正如drawable目錄和mipmap目錄有ldpi、mdpi、hdpi、xhdpi、xxhdpi之分。
*這裡解釋一下mipmap和drawable的區別
在Android studio開發中,新建一個module的時候不同於Eclipse(會生成drawable、drawable-ldpi、drawable-mdpi、drawable-hxpi等等),在資源文件中會生成mipmap-hdpi、mipmap-mdpi、mipmap-xhdpi、mipmap-xxhdpi和一個drawable目錄。
對於這個問題谷歌官方有說法:
drawable/
For bitmap files (PNG, JPEG, or GIF), 9-Patch image files, and XML files that describe Drawable shapes or Drawable objects that contain multiple states (normal, pressed, or focused).
意思是說以drawable開頭的目錄存放的文件有png、jpeg、gif格式圖片文件、.9圖片以及一些XML文件。
mipmap/
For app launcher icons.
而以mipmap開頭的目錄存放的是App的圖標。
dp
也叫dip(density independent pixel)直譯為密度無關的像素。我們猜測如果使用了這個單位,在不同屏幕密度的設備上顯示的長度就會是相同的。那麼在屏幕上顯示圖像時都是在屏幕上填充像素點,而使用這種與密度無關的像素(我們在布局文件中使用的 dp/dip 就是與密度無關的像素)是如何轉換成像素的呢?其實在Android中,將屏幕密度為160dpi的中密度設備屏幕作為基準屏幕,在這個屏幕中,1dp=1px。其他屏幕密度的設備按照比例換算,具體如下表:
由上表不難計算1dp在hdpi設備下等於1.5px,同樣的在xxhdpi設備下1dp=3px。這裡我們從dp到px解釋了Android中不同屏幕密度之間的像素比例關係。
下面換一個角度,從px到dp的變化來說明這種比例關係。
這裡我們選擇在以mipmap開頭的目錄中設計一個icon,要求icon在屏幕中佔據相同的dp。那麼對於不同的屏幕密度(MDPI、HDPI、XHDPI、XXHDPI 和 XXXHDPI)應按照 2:3:4:6:8 的比例進行縮放。比如尺寸為48x48dp,這表示在 MDPI 的屏幕上其實際尺寸應為 48x48px,在 HDPI 的屏幕上其實際大小是 MDPI 的 1.5 倍 (72x72 px),在 XDPI 的屏幕上其實際大小是 MDPI 的 2 倍 (96x96 px),依此類推。
*圖片的描述有兩種:
1、僅僅通過寬高的像素。
2、通過圖片解析度(不同於屏幕解析度,單位英寸中所包含的像素點數)和尺寸大小。
sp(scale-independent pixels)
與dp類似,但是可以在設置裡面調節字號的時候,文字會隨之改變。當系統字號設為「普通」時,sp與px的尺寸換算和dp與px是一樣的。
屏幕尺寸、屏幕解析度、屏幕密度
屏幕尺寸
設備的物理屏幕尺寸,指屏幕的對角線的長度,單位是英寸,1 inch = 2.54 cm。比如「5寸大屏手機」,就是指對角線的尺寸,5寸×2.54釐米/寸=12.7釐米。
屏幕解析度
也叫顯示解析度,是屏幕圖像的精密度,是指屏幕或者顯示器所能顯示的像素點有多少。一般以橫向像素×縱向像素表示解析度,如1200×1920表示此屏幕在寬度方向有1200個像素,在高度方向有1920個像素。
屏幕密度
是指每英寸上的像素點數,單位是dpi(dot per inch)或者ppi(pixels per inch),數值越高顯示越細膩。屏幕密度與屏幕尺寸和屏幕解析度有關。例如在屏幕尺寸一定的條件下,屏幕解析度越高屏幕密度越大,反之越小。同理在屏幕解析度一定的條件下,屏幕尺寸越小屏幕密度越大,反之越小。
屏幕尺寸和屏幕解析度,這兩個值是可以直接得到的。屏幕密度需要我們計算得到。例如我的手機的解析度是1200×1920,屏幕尺寸是7寸的。根據屏幕尺寸、屏幕解析度和屏幕密度定義不難看出他們之間的關係如下圖:
根據勾股定理,我們得出對角線的像素數大約是2264,那麼用2264除以7就是此屏幕的密度了,計算結果是323。
*備註:
上面的出現的0.75,1,1.5,2,3,4才是屏幕密度(density)。而120,160,240,320,480,640是屏幕密度dpi(densityDpi)。
實際密度與系統密度
實際密度就是我們自己算出來的密度,這個密度代表了屏幕真實的細膩程度,如上述例子中的323dpi就是實際密度,說明這塊屏幕每寸有323個像素。7英寸1200×1920的屏幕密度是323,5英寸1200×1920的屏幕密度是452,而相同解析度的4.5英寸屏幕密度是503。如此看來,屏幕密度將會出現很多數值,呈現嚴重的碎片化。而密度又是Android屏幕將界面進行縮放顯示的依據,那麼Android是如何適配這麼多屏幕的呢?
其實,每部Android手機屏幕都有一個初始的固定密度,這些數值是120、160、240、320、480,這些就是Android為不同設備設定的系統密度。
得到實際密度以後,一般會選擇一個最近的密度作為系統密度,系統密度是出廠預置的,如440dpi的系統密度就是和它最接近的480dpi;如果是330dpi的設備,它的系統密度就是320dpi。但是,現在很多手機不一定會選擇這些值作為系統密度,而是選擇實際的dpi作為系統密度,這就導致了很多手機的dpi也不是在這些值內。例如小米Note這樣的xxhdpi的設備他的系統密度並不是480,而是它的實際密度440。
獲取設備的上述屬性
Android系統中有個DisplayMetrics的類,通過這個類就可以得到上述的所有屬性。
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
float density = displayMetrics.density; //屏幕密度
int densityDpi = displayMetrics.densityDpi;//屏幕密度dpi
int heightPixels = displayMetrics.heightPixels;//屏幕高度的像素
int widthPixels = displayMetrics.widthPixels;//屏幕寬度的像素
String screenResolution = widthPixels + "X" + heightPixels;
float scaledDensity = displayMetrics.scaledDensity;//字體的放大係數
float xdpi = displayMetrics.xdpi;//寬度方向上的dpi
float ydpi = displayMetrics.ydpi;//高度方向上的dpi
其中xdpi = ydpi = densityDpi.列印結果如下:
上面計算的我的設備的dpi為323。這裡系統給定的屏幕密度dpi為320。
OK,基礎的東西介紹完了,那麼回歸正題:
1. android項目中那麼多以drawable開頭的文件夾,那應用的配圖應該放在哪個文件夾之下呢?
一般的開發的話,都會做三套配圖,對應著drawable-hdpi、drawable-xhdpi、drawable-xxhdpi三個文件夾。
2. 那要是做一套呢?應該做哪一套呢?
做一套也是可以的,放在drawable-xxdpi文件夾中。
3. 為什麼做一套配圖要放在drawable-xxdpi文件夾中?
這裡先不給出答案,我在網上也看了一些說法,說是省內存,這裡暫且不置可否。
明天我們就從內存的角度去分析問題3,使用同一張圖片,放置在不同的drawable文件夾,在同一設備上運行,研究一下圖片大小及內存的佔用。
如果你有好的技術文章想和大家分享,歡迎向我的公眾號投稿,投稿具體細節請在公眾號主頁點擊「投稿」菜單查看。