安卓項目實戰之與UI那點事:圖片適配你必須要瞭解的知識點

1,mipmap和drawable的區別

在Android4.2以上的版本中,提供了對mipmaps的支持,如果你用Andorid Studio開發Android程序會發現Android Studio自動幫你創建了幾個mipmaps文件夾,很多人每次新建一個工程的時候,總是先把mipmap刪掉,新建幾個不同dpi的drawable文件夾,纔開始幹別的。究竟mipmap和drawable有什麼區別呢?對此疑問進行一次總結如下:

首先我們先來糾正一個錯誤:mipmap文件夾是用來替代drawable文件夾的?
經多方查詢我們發現官網壓根沒說用mipmap文件夾替代drawable文件夾這樣的話,而且根據官方對於mipmap文件夾的介紹我們可以得出以下結論:
就目前而言,mipmap文件夾僅僅是用來存放應用的啓動圖標和與縮放動畫相關的圖片的,AndroidStudio新建項目的ic_launcher.png都是默認放在mipmap文件夾下的,這樣無論何種屏幕分辨率,系統都會選擇最適合的分辨率的icon顯示在主屏上。
而其他的圖片資源等,還是按照以前方式,放在drawable文件夾下,如位圖文件(PNG、JPEG、GIF),還有點九圖片和XML文件(shape和selector等)。
擴展:
mipmap指的是一種紋理映射技術,是目前解決紋理分辨率與視點距離關係的最有效途徑,mipmap是Android系統爲了解決不同分辨率顯示高清圖標而採用的一個技術,來使圖標保證清晰且適配各種屏幕。而mipmap也可以用在需要在動畫中被縮放的圖片,關於mipmap紋理映射技術的具體實現細節如果感興趣請讀者自行去查詢資料瞭解。

2,mipmap應用啓動圖標適配

講過上面的瞭解我們知道mipmap文件夾只是用來放置應用程序的啓動圖標icon的,僅此而已,並且系統對於mipmaps的支持也是從Android4.2以上版本纔開始的,將icon放置在mipmap文件夾還可以讓我們程序的launcher圖標自動擁有跨設備密度展示的能力,比如說一臺屏幕密度是xxhdpi的設備可以自動加載mipmap-xxxhdpi下的icon來作爲應用程序的launcher圖標,這樣圖標看上去就會更加細膩。
對於每種密度下的icon應該設計成什麼尺寸其實Android也是給出了最佳建議,icon的尺寸最好不要隨意設計,因爲過低的分辨率會造成圖標模糊,而過高的分辨率只會徒增APK大小。建議尺寸如下表所示:
在這裏插入圖片描述
然後我們引用mipmap的方式和之前引用drawable的方式是完全一致的,在資源中就使用@mipmap/res_id,在代碼就使用R.mipmap.res_id即可。
建議:我們至少需要提供一個xxxhdpi類型的啓動圖標,因爲Android會幫你自動縮小圖標到對應的別的分辨率上(放大是會變模糊的),這樣子可以節省些apk size。

這裏主要講解了app應用圖標基於不同屏幕密度如何高清顯示的適配,關於Android 8.0應用圖標基於不同廠商手機設備的外形適配請查看另一篇博客:你必須瞭解的Android 8.0中系統應用圖標適配

3,drawable圖片適配(重點)

在Android項目當中,drawable文件夾都是用來放置圖片資源的,不管是jpg、png、還是9.png,都可以放在這裏。除此之外,還有像selector這樣的xml文件也是可以放在drawable文件夾下面的。
一般根據設備dpi(dpi是指每英寸的像素)的不同我們需要建立不同的drawable文件夾,如下圖:
在這裏插入圖片描述
可以看到dpi可大致分爲mdpi,hdpi,xhdpi,xxhdpi,xxxdpi文件夾,按照安卓官方的適配建議需要每個文件夾中都放置相對應的圖片,這樣一來一個圖片就會有多個,有的人可能認爲這樣會增大工作量,只使用一套圖放置一個文件夾,這樣減輕UI人員和開發人員的工作,但是這樣會造成另一個問題,就是會造成內存問題,接下來我們具體來看下是如何造成內存問題的:
首先我準備了一張270*480像素的圖片:
在這裏插入圖片描述
將圖片命名爲android_logo.png,然後把它放在drawable-xxhdpi文件夾下面。爲什麼要放在這個文件夾下呢?是因爲我的手機屏幕的密度就是xxhdpi的。那麼怎麼才能知道自己手機屏幕的密度呢?你可以使用如下方法先獲取到屏幕的dpi值:

float xdpi = getResources().getDisplayMetrics().xdpi;
float ydpi = getResources().getDisplayMetrics().ydpi;

其中xdpi代表屏幕寬度的dpi值,ydpi代表屏幕高度的dpi值,通常這兩個值都是近乎相等或者極其接近的,在我的手機上這兩個值都約等於403。那麼403又代表着什麼意思呢?我們直接參考下面這個表格就知道了:
在這裏插入圖片描述
從表中可以看出,403dpi是處於320dpi到480dpi之間的,因此屬於xxhdpi的範圍。
圖片放好了之後,下面我在佈局文件中引用這張圖片,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
   >

    <ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/android_logo"
        />

</LinearLayout>

在ImageView控件中指定加載android_logo這張圖,並把ImageView控件的寬高都設置成wrap_content,這樣圖片有多大,我們的控件就會有多大。
現在運行一下程序,效果如下所示:
在這裏插入圖片描述
由於我的手機分辨率是10801920像素的,而這張圖片的分辨率是270480像素的,剛好是手機分辨率的四分之一,因此從上圖中也可以看出,android_logo圖片的寬和高大概都佔據了屏幕寬高的四分之一左右,大小基本是比較精準的。
關於手機屏幕的分辨率信息我們可以通過在Activity中調用如下代碼來獲取:

DisplayMetrics dm = new DisplayMetrics();
this.getWindowManager().getDefaultDisplay().getMetrics(dm);
int screenWidth = dm.widthPixels;
int screenHeight = dm.heightPixels;

接着我們嘗試做點改變,將android_logo.png這張圖移動到drawable-xhdpi文件夾下,注意不是複製一份到drawable-xhdpi文件夾下,而是將圖片移動到drawable-xhdpi文件夾下,然後重新運行一下程序,效果如下圖所示:
在這裏插入圖片描述
嗯?怎麼感覺圖片好像變大了一點,是錯覺嗎?
那麼我們再將這張圖移動到drawable-mdpi文件夾下試試,重新運行程序,效果如下圖所示:
在這裏插入圖片描述
這次肯定不是錯覺了,這實在是太明顯了,圖片被放大了!
那麼爲什麼好端端的一張圖片會被自動放大呢?而且這放大的比例是不是有點太過份了。其實不然,Android所做的這些縮放操作都是有它嚴格的規定和算法的。可能有不少做了很多年Android的朋友都沒去留意過這些縮放的規則,因爲這些細節太微小了,那麼本篇的微技巧探索裏面,我們就來把這些細節理理清楚。

首先解釋一下圖片爲什麼會被放大,當我們使用資源id來去引用一張圖片時,Android會使用一些規則來去幫我們匹配最適合的圖片。具體的查找規則如下:

  1. 先查找和屏幕密度最匹配的文件夾。比如上例中我的手機屏幕密度是xxhdpi,那麼系統會優先去drawable-xxhdpi文件夾下去查找,如果有的話就使用,此時圖片是不會被放大或者縮小的。
  2. 如果在最匹配的目錄沒有找到對應圖片,就會向更高密度的目錄查找,直到沒有更高密度的目錄。上例中更高密度的目錄就是drawable-xxxhdpi文件夾了,然後發現這裏也沒有android_logo這張圖,接下來會嘗試再找更高密度的文件夾,發現沒有更高密度的了。
  3. 如果一直往高密度目錄均沒有查找,Android就會查找drawable-nodpi目錄。drawable-nodpi目錄中的資源適用於所有密度的設備,不管當前屏幕的密度如何,系統都不會縮放此目錄中的資源。因此,對於永遠不希望系統縮放的資源,最簡單的方法就是放在此目錄中;同時,放在該目錄中的資源最好不要再放到其他drawable目錄下了,避免得到非預期的效果。
  4. 如果在drawable-nodpi目錄也沒有查找到,系統就會向比最匹配目錄密度低的目錄依次查找,直到沒有更低密度的目錄。例如,上例中最匹配目錄是xxhdpi,更高密度的目錄和nodpi目錄查找不到後,就會依次查找drawable-xhdpi -> drawable-hdpi -> drawable-mdpi -> drawable-ldpi。

總體匹配規則就是這樣,比如說此時在屏幕密度爲xxhdpi的設備上,系統優先去drawable-xxhdpi文件夾下去查找並沒有找到,那麼繼續往更高密度找也沒有找到,最後終於在drawable-mdpi文件夾下面找到android_logo這張圖了,但是系統會認爲你這張圖是專門爲低密度的設備所設計的,如果直接將這張圖在當前的高密度設備上使用就有可能會出現像素過低的情況,於是系統自動幫我們做了這樣一個放大操作,圖片放大則像素增加,必然會引起內存佔用量增加。
那麼同樣的道理,如果系統是在drawable-xxxhdpi文件夾下面找到這張圖的話,它會認爲這張圖是爲更高密度的設備所設計的,如果直接將這張圖在當前設備上使用就有可能會出現像素過高的情況,於是會自動幫我們做一個縮小的操作,圖片縮小則像素減少,內存佔用量就會降低。
所以,我們可以嘗試將android_logo這張圖移動到drawable-xxxhdpi文件夾下面將會得到這樣的結果:
在這裏插入圖片描述
可以看到,現在圖片的寬和高都達到不手機屏幕的四分之一,說明圖片確實是被縮小了。
接下來我們來看下在實際開發當中會遇到的場景:根據Android的開發建議,我們在準備圖片資源時儘量應該給每種密度的設備都準備一套,這樣程序的適配性就可以達到最好,但也存在問題,一是這種方式會增大安裝包的大小;二是很多公司UI在出圖時只會出一套。那麼在這種情況下,我們應該將僅有的這一套圖片資源放在哪個密度的文件夾下呢?
可以這樣來分析,根據我們剛纔所學的內容,如果將一張圖片放在低密度文件夾下,那麼在高密度設備上顯示圖片時就會被自動放大,而如果將一張圖片放在高密度文件夾下,那麼在低密度設備上顯示圖片時就會被自動縮小。那我們可以通過成本的方式來評估一下,一張原圖片被縮小了之後顯示其實並沒有什麼副作用,但是一張原圖片被放大了之後顯示就意味着要佔用更多的內存了。因爲圖片被放大了,像素點也就變多了,而每個像素點都是要佔用內存的。
內存的使用量可通過Android Monitor來查看,首先將android_logo.png圖片移動到drawable-xxhdpi目錄下,運行程序後我們通過Android Monitor來觀察程序內存使用情況: (設備和目錄級別相一致的最優情況)
在這裏插入圖片描述
可以看到,程序所佔用的內存大概穩定在19.45M左右。然後將android_logo.png圖片移動到drawable-mdpi目錄下,重新運行程序,結果如下圖所示: (低密度文件夾下圖片在高密度設備上顯示時會被放大)
在這裏插入圖片描述
現在漲到23.40M了,佔用內存明顯增加了,可以看到,僅僅一張圖片的內存佔用差別就已經在MB級別了。如果你將圖片移動到drawable-ldpi目錄下,你會發現佔用內存會更高。圖片放大的內存成本將是不得不考慮的一個重要因素了。

那麼經過上面一系列的分析,答案自然也就出來了,圖片資源應該儘量放在高密度文件夾下,這樣可以節省圖片的內存開支,而UI在設計圖片的時候也應該儘量面向高密度屏幕的設備來進行設計。由於目前的Android智能手機的屏幕基本都在1080p了,屏幕的dpi多數都處於320~480,爲了更好地適配,同時爲了節省內存成本,建議將切圖放置在drawable-xxhdpi目錄,同時建議UI針對該密度的設備設計切圖。那麼有的朋友可能會問了,不是還有更高密度的drawable-xxxhdpi嗎?幹嗎不放在這裏?這是因爲,市面上480dpi到640dpi的設備實在是太少了,如果針對這種級別的屏幕密度來設計圖片,圖片在不縮放的情況下本身就已經很大了,基本也起不到節省內存開支的作用了。

4,UI切圖px標註轉dp

一款優秀app的產生,往往需要有一套精美華麗的UI設計圖,誠然,UI僅僅只是個開始,有追求極致的前端工程師開發軟件時儘可能地去貼近UI的設計纔是重中之重。

我們知道,Android的尺寸單位一般採用dp或者sp,然而有時候我們遇到的UI設計圖給的尺寸標註卻是px的,這顯然是給iOS畫的UI。安卓設備的多樣性決定了我們絕對不能將控件的尺寸大小直接設置爲UI圖上的px值。那該如何解決呢?憤憤不平地去找UI工程師出一套安卓的標註?條件允許的話你當然可以這樣幹,但其實我們還有另外一種快準不知道狠不狠的解決方案:px轉dp。

我們知道px轉dp的公式爲:dp = px/density
上面density指是設備密度,有了設備密度,我們纔可以將px轉爲dp。而Android系統也爲我們提供了獲取設備密度的方法:

context.getResources().getDisplayMetrics().density;

獲取到了我們測試手機的設備密度之後,然後將UI圖上標註的px去除以desity?
當然不是!!!,density值是獲取了,但是請問UI圖上的px值是按照你的手機來標註的嗎?也就是說,我們必須要獲取UI圖在設計時基於的設備的設備密度(density)。

設備密度公式:density = PPI/160。
PPI是像素密度,公式:PPI = √(長度像素數² + 寬度像素數²) / 屏幕尺寸
上面PPI的公式不難理解,就是指每英寸屏幕有多少個像素點。比如iPhone6的PPI是326,1英寸屏幕有326個像素點。至於設備密度這個公式,PPI除以160,爲什麼是160而不是別的,這個不用太過於糾結。160是谷歌推薦的數值,這樣轉換爲hdpi、xhdpi等的數值就比較妥當。

根據上面的公式我們就有了如下計算設備密度density的方法:

int width = 750;//屏幕寬度
int height = 1334;//屏幕高度
float screenInch = 4.7f;//屏幕尺寸
//設備密度公式
float density = (float) Math.sqrt(width * width + height * height) / screenInch / 160;

注意上面三個變量值是UI切圖時所基於設備的取值。問UI工程師,問ta是以哪個尺寸爲基準進行畫圖的。也有個神器叫PxCook能識別出UI圖的設備型號基準,然後通過設備型號搜索出該設備是幾寸屏。

一般在Android中px與dp的關係:

dp可以保證在不同屏幕像素密度的設備上顯示相同的效果,而ui設計師給你的設計圖是以px爲單位的,Android開發則是使用dp作爲單位的,那麼我們需要進行轉換:
在這裏插入圖片描述
在Android中,規定以160dpi(即屏幕分辨率爲320x480)爲基準:1dp=1px

例如我之前所在公司的UI切的圖都是基於1280*720分辨率的設備切的圖,所以對於他在圖上所標出的px我都是直接除以2得到dp值然後來使用的。

dp,sp與px之間的轉換工具類:

/**
 * dp,sp 和 px 轉換的輔助類
 */
public class DisplayUtil {

    /**
     * 將px值轉換爲dip或dp值,保證尺寸大小不變
     * DisplayMetrics類中屬性density
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

    /**
     * 將dip或dp值轉換爲px值,保證尺寸大小不變
     * DisplayMetrics類中屬性density
     */
    public static int dip2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    /**
     * 將px值轉換爲sp值,保證文字大小不變
     * DisplayMetrics類中屬性scaledDensity
     */
    public static int px2sp(Context context, float pxValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (pxValue / fontScale + 0.5f);
    }

    /**
     * 將sp值轉換爲px值,保證文字大小不變
     * DisplayMetrics類中屬性scaledDensity
     */
    public static int sp2px(Context context, float spValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章