1. 背景
維基百科中的定義:
可縮放向量圖形(Scalable Vector Graphics,SVG)是一種基于可擴(kuò)展標(biāo)記語言(XML),用于描述二維向量圖形的圖形格式。SVG由W3C制定,是一個開放標(biāo)準(zhǔn)。
矢量圖的優(yōu)點
- SVG何以可以任意縮放而不會失真
- SVG文件一般都比較小,使用矢量圖資源達(dá)到apk縮包的效果
- SVG占用內(nèi)存非常小,性能高。但是SVG明顯的缺點是沒有位圖表達(dá)的色彩豐富。
Android 5.0(API 級別 21)開始提供矢量圖支持。對于之前版本的支持請參考矢量圖向后兼容性解決方案。Android中矢量圖對應(yīng)的類為VectorDrawable,對于矢量動畫類為AnimatedVectorDrawable,關(guān)于矢量動畫這部分放在下篇介紹。
2. 矢量圖使用
UI提供的矢量圖或者我們通過Photoshop制作的矢量圖都是為.svg格式,在Android項目中使用,需要將其轉(zhuǎn)化為xml文件,具體轉(zhuǎn)化參考運行 Vector Asset Studio
轉(zhuǎn)化后的xml樣子如下圖:

根標(biāo)簽為vector,第一次看上去對一大串不知何方神圣的數(shù)字和標(biāo)簽搞懵逼,但了解后就會發(fā)現(xiàn)從未如此簡單。
該xml文件使用起來和普通的xml Drawable完全相同,只是它對應(yīng)的VectorDrawable對象。
2.1 矢量圖xml文件結(jié)構(gòu)
根標(biāo)簽為vector,它由group,path和clip-path標(biāo)簽組成一個樹狀結(jié)構(gòu),如圖:

- vector標(biāo)簽:該標(biāo)簽標(biāo)識該xml文件是矢量圖,同時在該標(biāo)簽中可以設(shè)置矢量圖的大小。
-
group標(biāo)簽:類似View中的ViewGroup,
group,path和clip-path標(biāo)簽均可以作為其子標(biāo)簽,用于對group包含的部分進(jìn)行scale、rotate和translate和動畫 - path標(biāo)簽:類似繪制中的Path類,用于真正構(gòu)建圖形,構(gòu)建方式和Path也類似
-
clip-path標(biāo)簽:對矢量樹結(jié)構(gòu)中
clip-path同級及子節(jié)點按照指定區(qū)域進(jìn)行剪切,最終呈現(xiàn)的是指定區(qū)域中顯示的部分。
下面對這四部分詳細(xì)說明:
vector標(biāo)簽
| 屬性 | 說明 |
|---|---|
| name | 矢量圖名稱,起標(biāo)識作用 |
| width | 矢量圖寬度,只在View為wrap_content時起作用,支持所有單位px,dp... |
| height | 同上 |
| viewportWidth | 虛擬畫布的寬度,只用于繪制矢量圖內(nèi)容時使用 |
| viewportHeight | 同上 |
| tint | 通過tint可以指定該矢量圖的整體表面顏色 |
| tintMode | 色彩的Porter-Duff混合模式。 默認(rèn)為src_in |
| autoMirrored | 是否開啟鏡像,當(dāng)為true時,圖形在RTL布局時,圖片會鏡像顯示,相當(dāng)于沿中心Y軸旋轉(zhuǎn)180° |
| alpha | 矢量圖的透明度,范圍0-1,默認(rèn)1不透明; |
這些屬性都比較容易理解,對于tineMode這塊提一下
PorterDuff是什么意思呢?PorterDuff是兩個人名的組合: Thomas Porter和Tom Duff,他們1984年在ACM SIGGRAPH計算機(jī)圖形學(xué)發(fā)表論文《Compositing digital images》,最早提出圖形混合概念,極大地推動了圖形圖像學(xué)的發(fā)展,有興趣的同學(xué)可以自行查閱資料。

注意:width,height,viewportWidth和viewportHeight必須指定,否則無法顯示出圖形
path標(biāo)簽
| 屬性 | 說明 |
|---|---|
| name | path名稱,標(biāo)識唯一path |
| pathData | path的路徑數(shù)據(jù),類似Path類中的moveTo,lineTo等 |
| fillColor | 填充path的顏色,默認(rèn)不填充 |
| strokeColor | 畫筆軌跡顏色 |
| strokeWidth | 畫筆軌跡寬度 |
| strokeAlpha | 畫筆軌跡透明度 |
| fillAlpha | 填充透明度 |
| trimPathStart | 從路徑起始位置截斷路徑的比率,取值范圍0-1,默認(rèn)0 效果就是從開始到截斷[0,x.x]的部分沒有內(nèi)容 |
| trimPathEnd | 從路徑結(jié)束位置截斷路徑的比率,取值范圍0-1,默認(rèn)1 效果就是從結(jié)束到截斷[x.x,1]的部分沒有內(nèi)容 |
| trimPathOffset | 設(shè)置路徑截取時的偏移比例,取值范圍0-1,默認(rèn)0 相當(dāng)于將開始移動到指定的x.x比例處,需與trimPathStart或者trimPathEnd結(jié)合使用 |
| fillType | API 24引入該屬性,取值nonZero和evenOdd,默認(rèn)nonZero |
很多屬性都比較熟悉,這里就只對重要難懂的屬性進(jìn)行講解:
pathData
該屬性是繪圖核心,其使用的指令和Android中Path操作非常類似,常用指令如下,關(guān)于更多更全請參考w3 SVG Path章節(jié)
| 指令 | 類比Path方法 | 說明 |
|---|---|---|
| Mx,y | moveTo(x,y) | 移動到點(x,y) |
| Lx,y | lineTo(x,y) | 直線連接至點(x,y) |
| Hx | lineTo(x,原y) | 水平連接 |
| Vy | lintTo(原x,y) | 垂直連接 |
| Qx1,y1 x2,y2 | quadTo(x1,y1,x2,y2) | 二階貝塞爾曲線,控制點(x1,y1),終點(x2,y2) |
| Cx1,y1 x2,y2 x3,y3 | cubicTo(x1,y1,x2,y2,x3,y3) | 三階貝賽爾曲線,控制點(x1,y1),(x2,y2),終點(x3,y3) |
| Arx,ry x-axis-rotation large-arc-flag,sweep-flag x,y | 效果和arcTo類似 | 畫橢圓弧型,(rx,ry)為橢圓的x軸和y軸半徑,x-axis-rotation為橢圓x軸旋轉(zhuǎn)角度,當(dāng)前點和末尾參數(shù)點(x,y)為橢圓上的起點和終點,large-arc-flag和sweep-flag共同決定使用由起點和終點組成的哪個圓弧 |
| Z(z) | close() | 終點和起始點如果可以構(gòu)成封閉圖形則進(jìn)行連接 |
指令大小寫的區(qū)別:大寫指令使用的是絕對坐標(biāo),小寫指令使用相對上一個點的坐標(biāo)
M L H V指令代碼示例:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="60dp"
android:height="60dp"
android:viewportWidth="60"
android:viewportHeight="60">
<path
android:pathData="M10,10h30v30H10z,"
android:strokeWidth="1"
android:strokeColor="#FF0000" />
</vector>
效果圖:

M10,10移動到(10,10)處,h30則是水平連接至(10+30,10)點處,也就是點(40,10),v30為垂直連接至(40,10+30)處,也就是點(40,40),H10為水平連接至(10,40)處,最后通過z組成封閉圖形,也就是這個正方形了。
Qx1,y1 x2,y2 二階貝賽爾曲線
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="60dp"
android:height="60dp"
android:viewportWidth="60"
android:viewportHeight="60">
<path
android:pathData="M0,0H60V60H0Z"
android:strokeWidth="1"
android:strokeColor="#0000FF" />
<path
android:pathData="M0,30Q30,0 60,30"
android:strokeWidth="1"
android:strokeColor="#FF0000" />
<path
android:pathData="M0,60Q50,30 60,60"
android:strokeWidth="1"
android:strokeColor="#FF0000" />
</vector>

藍(lán)色線條為繪制區(qū)域,第二個path為紅色線條,第三個path為綠色線條。以第二個path為例,點(0,30)為起始點,點(60,30)為終點,控制點為(30,0),從而構(gòu)成了如圖的二階貝塞爾曲線。
Cx1,y1 x2,y2 x3,y3 三階貝塞爾曲線
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="60dp"
android:height="60dp"
android:viewportWidth="60"
android:viewportHeight="60">
<path
android:pathData="M0,0H60V60H0Z"
android:strokeWidth="1"
android:strokeColor="#0000FF" />
<path
android:pathData="M0,30C20,0 40,0 60,30"
android:strokeWidth="1"
android:strokeColor="#FF0000" />
<path
android:pathData="M0,60C10,30 50,30 60,60"
android:strokeWidth="1"
android:strokeColor="#00FF00" />
</vector>

藍(lán)色依然是可繪制區(qū)域,第二個path為紅色線條,第三個path為綠色線條,以第二個path為例,起點為(0,30),終點為(60,30),第一個控制點為(20,0),第二個控制點為(40,0),從而構(gòu)成了如圖美麗的三階貝塞爾曲線。
Arx,ry x-axis-rotation large-arc-flag,sweep-flag x,y 繪制橢圓弧
知道你不理解的還是large-arc-flag和sweep-flag這兩個屬性,這里會進(jìn)行說明,不過我們先思考個簡單問題,假如知道一個橢圓上的兩個點,可以確定出幾個橢圓?你肯定會說兩個,恭喜你答對了,當(dāng)然如果這兩個點為一個軸上與橢圓相交兩個點時,此時兩橢圓重合。
那么如果我們不畫橢圓,只是畫橢圓弧,理所應(yīng)當(dāng)伴隨著弧線分為長短,共可以構(gòu)建出4個弧線。對于如何區(qū)分橢圓我們通過起始點和終止點是順時針還是逆時針即可確定,對于長弧和短弧通過一個flag即可確定,有了這兩個flag我們就可以唯一確定一條弧線,到這謎底就揭曉了,arge-arc-flag,sweep-flag就是來確定唯一的弧線。結(jié)合下圖更容易理解哦

| 參數(shù) | 取值 | 說明 |
|---|---|---|
| arge-arc-flag | 1 | 大弧線 |
| arge-arc-flag | 0 | 小弧線 |
| sweep-flag | 1 | 順時針 |
| sweep-flag | 0 | 逆時針 |
代碼示例:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="60dp"
android:height="60dp"
android:viewportWidth="60"
android:viewportHeight="60">
<!--繪制藍(lán)色可繪制正方形區(qū)域-->
<path
android:pathData="M0,0H60V60H0Z"
android:strokeWidth="1"
android:strokeColor="#0000FF" />
<!--繪制紅色短弧線-->
<path
android:pathData="M20,20A20,20 0 0,0 40,40"
android:strokeWidth="1"
android:strokeColor="#FF0000" />
<!--繪制紅色長弧線-->
<path
android:pathData="M20,20A20,20 0 1,0 40,40"
android:strokeWidth="1"
android:strokeColor="#FF0000" />
<!--繪制綠色段弧線-->
<path
android:pathData="M20,20A20,20 0 0,1 40,40"
android:strokeWidth="1"
android:strokeColor="#00FF00" />
<!--繪制綠色長弧線-->
<path
android:pathData="M20,20A20,20 0 1,1 40,40"
android:strokeWidth="1"
android:strokeColor="#00FF00" />
</vector>

group標(biāo)簽
| 屬性 | 說明 |
|---|---|
| name | group名稱,唯一標(biāo)識group |
| rotation | group的旋轉(zhuǎn)角度,默認(rèn)0 |
| pivotX,pivotY | scale和rotation變換中心點的x,y坐標(biāo),默認(rèn)(0,0); |
| scaleX,scaleY | X和Y軸方向的縮放,默認(rèn)1; |
| translateX,translateY | X和Y軸方向的移動距離,默認(rèn)0 |
group標(biāo)簽可以進(jìn)行放大縮小,平移,旋轉(zhuǎn)操作,操作的對象就是group子標(biāo)簽所形成圖形,該部分內(nèi)容比較容易理解,就不寫示例代碼了。
clip-path標(biāo)簽
clip-path標(biāo)簽只有兩個屬性,name和pathData,pathData中定義了截取顯示的區(qū)域,clip-path標(biāo)簽生效的范圍為其所在的父標(biāo)簽(group或者vector)中clip-path位置以下的圖形生效
示例代碼:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="60dp"
android:height="60dp"
android:viewportWidth="60"
android:viewportHeight="60">
<!--繪制藍(lán)色可繪制正方形區(qū)域-->
<path
android:pathData="M0,0H60V60H0Z"
android:strokeWidth="1"
android:strokeColor="#0000FF" />
<clip-path android:pathData="M20,20 L40,40 L20,40" />
<group>
<!--繪制正方形-->
<path
android:pathData="M20,20h20v20h-20z"
android:strokeWidth="1"
android:strokeColor="#FF0000" />
</group>
</vector>
繪制在中心位置的是正常形,clip-path構(gòu)建的剪切區(qū)域為這個正方形的左下角三角形區(qū)域,看效果已正常剪切了。

將<clip-path android:pathData="M20,20 L40,40 L20,40" />調(diào)整在vector結(jié)束標(biāo)簽之前或者示例中g(shù)roup結(jié)束標(biāo)簽之前,效果如圖

均為達(dá)到剪切的目的,其實也是有原因的,因為vector矢量圖中元素是有順序的,且后面無法影響之前的操作。
3. 總結(jié)
對于矢量圖基礎(chǔ)部分已經(jīng)介紹完了,也足以應(yīng)對工作的需要,當(dāng)然更炫酷的操作還是矢量圖動畫,這部分會在下一篇講解,期待吧!
哦,制作和轉(zhuǎn)化svg矢量圖為xml還沒提,下面這兩個工具供你使用。
- 制作簡單的矢量圖網(wǎng)站:https://editor.method.ac/
- 轉(zhuǎn)化矢量圖為xml文件:AndroidStudio即可完成,具體參考https://developer.android.com/studio/write/vector-asset-studio#running