擁抱SVG:苦惱於圖片適配 in Android?

lypeer發表於2016-09-19

前言

不管是開發 Android 已久的老司機,還是剛剛上車的新司機,都肯定會對一件事情深惡痛絕:圖片適配(尤其是在美工不給力的條件下)!為什麼 Android 手機要有這麼多不同的解析度? 為什麼我的圖片在這臺手機上顯示地好好的完全符合設計圖的要求結果換到另一臺手機上就變形了?Oh my god !

以前為了解決圖片在不同的解析度的螢幕上顯示不一致的問題,通常我們會採取兩種方式:一是根據不同的解析度建立不同的資源包,然後每個要用到的圖片資源都需要做成多種尺寸的以適配不同的解析度;二是乾脆直接放一個解析度比較大的圖片,然後欽定 ImageView 的大小,強行把圖片塞進去——由於圖片的解析度比較大,所以在大部分機型上也還是能看的。但是這兩種方式都有一些問題,首先不可避免的,都極容易導致 apk 包的體積變大,這個問題在 app 體量比較小的時候可能不會對產品造成什麼干擾,但是當體量逐漸變大,這將是個極為讓人頭疼的問題。另外,用第一種方法完成過圖片適配的兄弟都懂,把不同尺寸的圖片更名為符合規範的相同名字並放到它們合適的資料夾裡面去簡直是一種折磨。。。

這樣的痛苦體驗什麼時候才是個頭?

擁抱SVG吧。

PS:我用 SVG 動畫寫了一個類似於 Google 2016 I/O 大會上的那個時鐘的東西,應該是一個比較好的學習 SVG 在 Android 上的使用的資源,大家可以去關注一下點點 star 提提 issue 哈,地址是:GoogleClock,貼個截圖:

擁抱SVG:苦惱於圖片適配 in Android?
GoogleClock

正文

1,什麼是 SVG ? && SVG VS 點陣圖

SVG是指可伸縮向量圖形 (Scalable Vector Graphics),它不同於傳統的點陣圖,不是通過儲存影象中每一點的畫素值來儲存與使用圖形,而是通過 XML 檔案來定義一個圖形,通過一些特定的語法和規則來繪製出我們所需的影象——同樣是使用一張圖片,SVG 的方式是事先定義好怎麼去畫這個圖,然後等要用的時候再把它去畫出來,而使用傳統的點陣圖的話就是已經有了畫出來的圖,然後要用的時候直接把畫好的圖拿出來用。這樣一來的話我們就很容易可以分析出它們兩種方式之間的優劣之處:

  • SVG 是在要用圖的時候再把圖畫出來,所以理所當然的在圖片顯示的時候會花費更多的時間消耗更多的資源。
  • 同樣由於上一個原因, SVG
    並不太適合層次過於複雜細節過於繁多的圖片。
  • 點陣圖是事先已經畫好的圖片,所以適應性必然沒有 SVG 好,同一張圖片在不同解析度下顯示會有差異。
  • SVG 的檔案裡儲存了繪製圖片的相關資訊,所以我們能夠對圖片的線條有一個非常清晰的感知,這在做動畫的時候特別有用。
  • SVG 沒有儲存任何影象的畫素資訊,所以 SVG 的檔案體積遠小於傳統的點陣圖檔案。
  • SVG 的檔案畫出來的影象是向量圖,所以不會存在失真的問題,理論上支援任何級別的縮放。

從上面的分析大家可以發現,在 SVG 的眾多優點下, 它的缺點幾乎可以忽略不計了(當然,前提是它所耗費的資源不會對使用者體驗造成較大的影響)——這也正是本文標題 “擁抱SVG” 的由來。

2,SVG in Android

既然 SVG 這麼好,那麼為什麼當前並沒有多少 Android 應用是使用了 SVG 圖片的呢?因為市場。Android對於 SVG 的支援是從 Android L 開始的,它的 SDK 裡面加入了 VectorDrawable , AnimatedVectorDrawable 等類幫助我們構建 SVG 圖形以及動畫,並且你可以在 xml 檔案裡面直接使用 標籤繪製 SVG 影象以及 標籤為 SVG 影象分配動畫。

但是請注意,目前的各種相容方案,不管是官方出的相容包還是各種社群裡出現的一些方案,都沒有實際上的把它的相容做到盡善盡美的地步——這意味著,如果不想將就的話,就只能等到市場上基本都是 Android L 或以上的裝置的時候,才有可能在生產中大規模的全面的用 SVG 替換點陣圖了。而目前,2016年秋,據友盟統計,Android L 及以上的裝置的市場佔有率僅有 43.27% ,一半都不到——但是很顯然,距離 SVG 在 Android 上發力的時候沒有多遠了,畢竟目前在售賣的 Android 手機已經基本上都是搭載的 Android L 及以上的系統了,只待老裝置被淘汰。

3,SVG 的使用

3.1,獲得一個 SVG 檔案

要使用 SVG ,那麼首先我們肯定得有一個 SVG 檔案。我們一般都有兩種方式來獲得一個 SVG 檔案:自己寫一個 SVG 檔案,或者通過 AI 或一些網站作圖之後匯出它的 SVG 檔案。

3.1.1,自己寫一個 SVG 檔案(Android 中)

前面說過,SVG 檔案裡面儲存的是如何去繪製目標圖片的相關資訊,所以理論上我們是可以從 0 開始寫一個我們自己的 SVG 檔案的——只要知道它繪製檔案的規則,一切皆可繪製。我們先來看一下一個簡單的 SVG 檔案:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="132dp"
        android:height="132dp"
        android:viewportHeight="132.0"
        android:viewportWidth="132.0">
    <path
        android:pathData="M50,2 L80.813,2 L80.813,130 L50,130 L50,2 Z"
        android:strokeColor="#e33e2b"
        android:strokeWidth="8" />
</vector>複製程式碼

這個檔案繪製出來的圖形是這樣的(沒錯,在編寫 SVG 的 XML 檔案的時候 Android Studio 是可以預覽的,很強大):

擁抱SVG:苦惱於圖片適配 in Android?
繪製出來的圖案

可以看到,總的來說 SVG 檔案在 Android 中的載體是一個 <vector/> 標籤,而繪製圖片的工作是在 <path/> 這個子標籤裡面做的。我們先來看一下這兩個標籤裡面最常使用的一些屬性,更多的一些屬性我會單開一篇博文專門講這個。

vector:

屬性 引數型別 預設值 描述
width dimen 必填屬性 圖形的實際寬度,可在使用時根據需要再次定義
height dimen 必填屬性 圖形的實際長度,可在使用時根據需要再次定義
viewportHeight float 必填屬性 定義畫布的尺寸
viewportWidth float 必填屬性 定義畫布的尺寸

平時在 <vector/> 標籤裡面常用的屬性基本上也就上面這四個了,但是關於這四個屬性我想再多講一些東西,因為我發現目前網上有很多文章對這個的描述都有些問題,容易對剛接觸這個的同學產生誤導。首先如果對應到一張具體的圖片,XXX.png來講的話,上表中的 widthheight 就相當於 XXX.png 的實際的寬度和長度,但是不同的是我們可以在使用它的時候再任意的定義它的新的寬度和長度,甚至比例和原先不一樣都沒關係,圖形並不會因此而失真。而 viewportHeightviewportWidth 這兩個屬性則是用來確定 XXX.png 上的座標系的單位長度的。比方說,像上面的程式碼的話,viewportHeight 為 132 , heigth 為 132dp ,意思就是圖片的長度被分成了 132 份,每一份的長度為 1dp ——這樣一來,座標系的單位長度就有了,也就有了根據座標來繪製圖形的基礎。另外,在 Android 上 SVG 的畫布上的座標系是這樣的:

擁抱SVG:苦惱於圖片適配 in Android?
座標系

接下來看一下 <path/> 標籤的常用屬性:

屬性 引數型別 預設值 描述
pathData String 畫圖的核心所在,有一定的語法,根據它來繪製目標圖形
strokeColor color 透明 畫筆的顏色
name String 這一條path的name,在其他地方可以根據name來找到這一條path
strokeWidth float 0.0 畫筆的寬度
fillColor color 透明 用顏色填充繪製過的區域,如果圖形是閉合的就直接填充,如果圖形不是閉合的那麼就將圖形的起點和終點相連使其閉合然後填充

基本上知道了上面這些屬性就可以通過賦給 pathData 一些值來繪製出一些比較常規的圖形了。接下來我講一下在 pathData 裡面繪製圖形的一些基本語法:

  • M = moveto(M X,Y):將畫筆移動到指定的座標位置,但未發生繪製
  • L = lineto(L X,Y):畫直線到指定的座標位置
  • H = horizontal lineto(H X):畫水平線到指定的X軸座標
  • V = vertical lineto(V Y):畫垂直線到指定的Y軸座標
  • C = curveto(C X1,Y1,X2,Y2,ENDX,ENDY):三次貝塞曲線
  • S = smooth curveto(S X2,Y2,ENDX,ENDY):三次貝塞曲線
  • Q = quadratic Belzier curveto(Q X,Y,ENDX,ENDY):二次貝塞曲線
  • T = smooth quadratic Belzier curveto(T ENDX,ENDY):對映前面路徑後的終點
  • A = elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧線
  • Z = closepath():關閉路徑

另,上述所有指令大小寫均可。大寫表示絕對定位,參照全域性座標系;小寫表示相對定位,參照父容器座標系。指令和資料間的空格可以省略。同一指令出現多次可以只用一個。

現在我們可以稍微的解讀一下上面的那個例子裡面的 pathData 是什麼意思了。上面的資料是:

M50,2 L80.813,2 L80.813,130 L50,130 L50,2 Z
//大家可以看到,L 指令的後面只有一個座標,這樣的意思就是畫的時候以上一筆的終點為起點複製程式碼

根據我們的語法,他的意思就是:

  • M 50,2 :將畫筆移到(50,2) 座標處。
  • L 80.813,2 :從 (50,2) 畫直線到 (80.813,2)。
  • L 80.813,130 :從 (80.813,2) 畫直線到 (80.813,130)。
  • L 50,130 :從 (80.813,130) 畫直線到 (50,130)。
  • L 50,2 :從 (50,130) 畫直線到 (50,2)。
  • Z :結束這一筆。

我們不難發現,繪製的過程其實很簡單,最後的結果是形成了一個閉合的矩形——和我們的成型的影象是一致的。

在這裡我就不介紹更多的指令的使用了,具體的大家可以參照這篇文章:Android vector標籤 PathData 畫圖超詳解。不過其實我認為,根本沒有必要去把這些指令完全的記憶下來,因為在實際的工作中我們幾乎是不會去手寫 SVG 檔案的,我們需要掌握的指令其實只有兩個: M 和 Z 。因為它們象徵著畫筆的一筆的開始和結束,在涉及一些和路徑軌跡相關的操作的時候我們可以合理的控制 M 和 Z 來很快速的達到我們的目的。

3.1.2,獲取一個 SVG 檔案(Android 中)

在希望加入 SVG 檔案的地方右鍵一下,然後 new -> Vector Asset :

擁抱SVG:苦惱於圖片適配 in Android?

點一下 Vector Asset ,會彈出另一個新的彈窗,它是長這樣的:

擁抱SVG:苦惱於圖片適配 in Android?
選擇 MD icon 或者匯入現成的 SVG 檔案

圖中有兩個白色框框起來的部位,上面那個是一個單選框,可以很清晰的知道選第一個是選取 Material Icon (沒錯,就是這麼貼心,AS 自帶所有 MD 圖示的 SVG 檔案),第二個是選取本地 SVG 檔案,選好第一個框之後再在下面的白框裡面選擇具體的哪一個檔案——這樣一來,我們的目的就完成了,我們成功的在 AS 裡面得到了一個 SVG 的 XML 檔案。

在這裡可能很多同學會有一個問題:剛才說了如何將本地的 SVG 檔案匯入到 AS 中,那麼我們又怎麼獲得本地的 SVG 檔案呢?講道理,這個問題不應該是我們考慮的,這個東西應當是美工把製作好的圖給我們,然後我們直接使用就可以了。但是有的時候我們自己做小東西並沒有專業的美工怎麼辦呢?要麼你可以自己去學學 AI 或者 GIMP 等軟體的使用方法, 用它們來製作圖形然後導成 SVG ,當然這樣的話學習成本有點高——不過沒關係,我們還有低配版的實現方式Method Draw。這是一個線上製作向量圖的網站,可以很方便的將在上面製作的圖形匯出成 SVG 檔案,學習成本相當低,而且能完成我們大部分的需求,總之我覺得還挺好用的。

3.2,開始使用吧!

如果前面的工作都完成了的話,我們應當是已經有了一個 SVG 的 XML 檔案,接下來理所當然的是如何在我們需要的地方使用它了。那麼怎麼使用呢?

我想說的是,接下來你完全可以把它當成我們引入專案的一張圖片來用。比如:

擁抱SVG:苦惱於圖片適配 in Android?
在佈局檔案中使用 SVG

上圖是直接在佈局檔案中直接引用 SVG 的 XML 檔案,程式碼中的 ic_android_black_24dp 就是已經寫好的 SVG 檔案。可以看到,直接把它當做一張普通的圖片來使用就可以了。當然,我們也可以在 Java 程式碼裡面來使用 SVG 檔案,像下面這樣:

mImageView.setImageDrawable(getDrawable(R.drawable.ic_android_black_24dp));複製程式碼

到這裡我們就已經可以在 Android 中使用 SVG 來作為圖片資源了,這樣一來不僅 Apk 包的體積得到了大大的減小,我們的圖片也具有了任意拉伸而不失真的特性,而且我們也再也不用非常痛苦的去搞圖片改名稱分包了。

結語

總的來講,我認為 SVG 是有在大部分應用場景下取代傳統的點陣圖成為一種更優的圖片的解決方案的潛力的,至少在 Android 中是這樣。但是由於目前搭載著 Android L 之前的機子還很多,所有很多的 Android 開發人員就將學習 SVG 相關的知識無限期的推遲了,但其實,已經是時候了。

另外,這篇文章更多的是一片科普性質的博文,主要是在介紹 SVG 的一些情況,包括它的好處啊,怎麼在 Android Studio 上獲取匯入啊什麼的,關於 SVG 在 Android 上的使用只涉及到了一些很皮毛的部分,更多的比較深入的東西會在後續博文中進一步闡述,敬請期待。

最後,再打一發廣告:我用 SVG 動畫寫了一個類似於 Google 2016 I/O 大會上的那個時鐘的東西,應該是一個比較好的學習 SVG 在 Android 上的使用的資源,大家可以去關注一下點點 star 提提 issue 哈,地址是:GoogleClock

相關文章