概述
2014年推出Android Lolliop 帶來了全新的設計語言——Material Design,同時也帶來VectorDrawable,标志着Android對矢量圖的支援開始了。
矢量圖的優勢主要有兩個:
一個是簡化Drawable資源的管理。
另一個是豐富多彩的矢量動畫。
衆所周知,Android運作在螢幕大小多樣、像素密度不一的裝置上。對于開發人員來說,為了更好的使用者體驗,通常需要提供各種像素密度對應的圖檔,甚至需要提供不同裝置的對應的圖檔。一方面提供與管理一個資源的多個版本比較麻煩,另一方面也會導緻最後生成的安裝包變大。而使用矢量圖就可以輕松解決這個問題,矢量圖隻需要一個xml描述檔案描述,而位圖需要記錄每個像素點的顔色值。同時,矢量圖在縮放時不會失真,不需要提供多個資源圖檔。
矢量圖還可以通過簡單的方式實作炫酷的矢量圖動畫。效果可以參考RichPath庫提供的效果圖檔,如下:

矢量圖動畫效果展示
矢量圖的缺陷:雖然矢量圖的優點很明顯,但是矢量圖并不能完全取代位圖,對于顔色較多較不規律的圖檔,矢量圖顯得力不從心。同時,由于需要在運作時根據xml描述檔案繪制圖形,需要比位圖消耗更多的性能。是以,矢量圖最好用在Icon圖示上。
Android Lolliop雖然帶來了VectorDrawable,但是并不意味着完全支援了矢量圖。實際上Android支援的是矢量圖示準中的Path部分。
矢量圖資源
在android中,矢量圖資源是一個用标簽描述xml檔案。可以自定義,也可以通過工具轉換svg檔案。如下圖:

建立VectorAsset
然後在Configure Vector Asset界面可以直接點選Icon後的圖形選擇SDK自帶的Material Icon,還可以切換到Local File模式,通過工具轉換SVG檔案或PSD檔案。
注意:
Android不完全支援SVG标準,轉換時會丢棄部分資訊,如漸變色之類的。
在轉化某些非path協定時,轉化效果也有點細節問題。比如轉化svg的ellipse時,它用了一系列的貝塞爾曲線Path來拟合橢圓,更簡化的方法應該是使用A指令來生成橢圓。
工具生成的xml内width/height屬性直接使用svg的畫布尺寸的,機關是dp,這樣的drawable可能會導緻性能問題,是以要改成自己需要的寬高。
矢量圖中path的定義與指令
每個指令大寫字母代表後面的參數是絕對坐标,而小寫字母表示相對坐标。
類型
功能
指令
描述
移動起始點
——
M (move to)
用法:M X,Y (X,Y)是Canvas上的點的位置,M指令會改變Path的初始點 ,如 M 10,10 或者 M10 10,Path從 (10, 10) 開始繪制。
m
用法:m x,y 假設目前點是 (X0, Y0),m指令會将Path的初始點改為(X0+x, Y0+y),如目前點為 (10, 10), m 10,5 指令後,Path從 (20, 15) 開始繪制。
畫直線
水準線
H (horizontal lineto)
用法:H X 從目前位置 (X0, Y0) 繪制直線到 (X, Y0)
h
用法:h x 從目前位置 (X0, Y0) 繪制直線到 (X0+x, Y0)
垂直線
V (vertical lineto)
用法:V Y 從目前位置 (X0, Y0) 繪制直線到 (X0, Y)
v
用法:v y 從目前位置 (X0, Y0) 繪制直線到 (X0, Y+y)
直線
L (lineto)
用法:L X,Y 從目前位置繪制直線到 (X, Y),如 L 10,10 或者 L10 10
l
用法:l x,y 表示從目前位置 (X0, Y0) 繪制直線到 (X0+x, Y0+y)
畫曲線
橢圓圓弧
A (elliptical Arc)
用法:繪制橢圓圓弧的參數比較複雜,如下:A rx ry x-axis-rotation large-arc-flag sweep-flag X Y,表示繪制一個橢圓圓弧經過(X,Y)點。
a
用法:繪制橢圓圓弧的參數比較複雜,如下:a rx ry x-axis-rotation large-arc-flag sweep-flag x y,表示從目前位置 (X0, Y0) 繪制一個橢圓圓弧經過 (X0+x, Y0+y) 點。
二次貝塞爾曲線
Q (quadratic Bézier curveto)
用法:Q X1,Y1 X,Y,從目前點到 (X, Y) 繪制一條控制點是 (X1, Y1) 的二次貝塞爾曲線,如Q 6,4 10,5,控制點是 (6, 4),最終點是(10,5)。
q
用法:q x1,y1 x,y,從目前點 (X0, Y0) 到 (X0+x, Y0+y) 繪制一條控制點是 (X0+x1, Y0+y1) 的二次貝塞爾曲線,如q 6,4 10,5 目前點是 (5, 5),控制點是 (11, 9),最終點是(15,10)。
T (smooth quadratic Bézier curveto)
用法:T X,Y 如果前面是一個Q/T指令,則自動計算一個保證起始點平滑的對稱控制點 (X1,Y1),從目前點到 (X, Y) 繪制一條控制點是(X1,Y1)的二次貝塞爾曲線。其它與上面Q指令相同。 如果T前面是一個非Q/T指令,則無法計算一個對稱的控制點,則從目前點到(X,Y)繪制一條直線(降階特性)。
t
用法:t x,y 功能與T指令相同 ,從目前點 (X0, Y0) 到 (X0+x, Y0+y) 繪制一條二次貝塞爾曲線
三次貝塞爾曲線
C (cubic bezier)
用法:C X1,Y1 X2,Y2 X,Y 從目前點到 (X, Y) 點繪制一條控制點是(X1,Y1)、(X2,Y2)的三次貝塞爾曲線,如C 6,4, 7,4, 10,5,控制點是(6,4)、(7,4),最終點是(10,5)。
c
用法:c x1,y1 x2,y2 x,y 從目前點 (X0,Y0) 到 (X0+x, Y0+y) 點繪制一條控制點是(X0+x1, Y0+y1)、(X0+x2, Y0+y2) 的三次貝塞爾曲線
S (smooth cubicto)
用法:S X2,Y2 X,Y 如果前面是一個C/S指令,則自動計算一個保證起始點平滑的對稱控制點 (X1,Y1),從目前點到 (X,Y) 繪制一條控制點是 (X1,Y1)、(X2, Y2) 的三次貝塞爾曲線。其它與上面C指令相同。如果S前面是一個非C/S指令,則無法計算第一個對稱的控制點,則從目前點到 (X, Y) 繪制一條控制點是 (X2, Y2) 的二次貝塞爾曲線(降階特性)。
s
s x2,y2 x,y 功能與S指令相同,從目前點 (X0, Y0) 到 (X0+x, Y0+y) 繪制一條控制點是 (X1,Y1)、(X0+x2, Y0+y2) 的三次貝塞爾曲線
閉合圖形
——
Z(close) / z
用法:一般Z指令用在一條Path的最末尾,但其實在Z指令後還可以再繼續新的路徑,不過Z指令不改變Path的初始點。閉合指令沒有參數,是以大小寫形式效果相同
橢圓圓弧詳解
橢圓圓弧 A rx ry x-axis-rotation large-arc-flag sweep-flag X Y
rx:橢圓橫軸半徑
ry:橢圓豎軸半徑
x-axis-rotation:橢圓橫軸相對于CanvasX軸的偏移角度
large-arc-flag:在前面三個參數确定的情況下,滿足目前點到指定點(X,Y)位置條件的圓弧總是有四條,此值取0表示繪制小弧度,取值1表示繪制大弧度
sweep-flag:在前面三個參數确定的情況下,滿足目前點到指定點(X,Y)位置條件的圓弧總是有四條,去掉上面large-arc-flag辨別後還有兩個,sweep-flag 取值0表示繪制逆時針方向的圓弧,取值1表示繪制順時針方向的圓弧。
X Y 繪制一個橢圓圓弧經過點 (X, Y)
示意圖:

二次貝塞爾曲線詳解
二次貝塞爾曲線 Q X1,Y1 X,Y
示意圖

二次貝塞爾曲線示範動畫,t在[0,1]區間
光滑二次貝塞爾曲線 T X,Y
T指令在Q指令之後:起始點為 (X0, Y0),Q X1,Y1 X,Y T Xm,Ym。從起始點 (X0, Y0) 到 (X, Y) 繪制一條控制點是 (X1, Y1) 的二次貝塞爾曲線。然後以 (X, Y) 為中心取 (X1, Y1) 的對稱點 (X', Y'),并以 (X', Y') 為控制點繪制一條從 (X, Y) 到 (Xm,Ym) 的二次貝塞爾曲線。(X', Y') 等于 (2X-X1, 2Y-Y1) 。

光滑二次貝塞爾曲線
T指令不在Q指令之後:因為無法計算一個對稱的控制點,是以以 (X, Y) 為控制點,相當于從目前點到 (X, Y) 繪制一條直線(降階特性)。

三次貝塞爾曲線詳解
三次貝塞爾曲線 C X1,Y1 X2,Y2 X,Y
示意圖

三次貝塞爾曲線示範動畫,t在[0,1]區間
平滑三次貝塞爾曲線 S X2,Y2 X,Y
S指令在C指令之後:起始點為 (X0, Y0),C X1,Y1 X2,Y2 X,Y S X3,Y3 Xm,Ym。從起始點 (X0, Y0) 到 (X, Y) 繪制一條控制點是 (X1, Y1) 、(X2, Y2)的三次貝塞爾曲線。然後以 (X, Y) 為中心取 (X2, Y2) 的對稱點 (X', Y'),并以 (X', Y') 、(X3, Y3)為控制點繪制一條從 (X, Y) 到 (Xm,Ym) 的三次貝塞爾曲線。(X', Y') 等于 (2X-X2, 2Y-Y2) 。

S指令不在C指令之後:因為無法計算第一個對稱的控制點,則以目前點為第一個控制點,相當于從目前點到 (X, Y) 繪制一條控制點是 (X2, Y2) 的二次貝塞爾曲線(降階特性)。
Android中的VectorDrawable的屬性
VectorDrawable在XML檔案中以類似SVG格式定義一個靜态的圖像資源, 每個VectorDrawable都是通過group和path對象組成的樹狀結構。path用來描述幾何圖形,而group用來描述幾何圖形的變形。所有在xml檔案中定義的path都會繪制出來。
結構示意圖:

VectorDrawable結構
根結點的屬性
屬性名
描述
android:name
定義矢量圖形的名稱
android:width
定義Drawable的寬度,支援所有dimension機關,一般使用dp。drawable的寬度不一定是最終繪制寬度,比如給ImageView設定backgroud則Drawable繪制寬度等于ImageView的寬度,給ImageView設定src則在ImageView大于Drawable寬度時,Drawable繪制寬度等于自己定義的寬度。
android:height
定義Drawable的寬度,支援所有dimension機關,一般是dp。其它同上。
android:viewportWidth
定義矢量圖形的視圖(viewport)空間的寬度,viewport是一個虛拟的canvas,後面所有的path都在該坐标系上繪制。坐标系左上方為(0,0),橫軸從左向右,縱軸從上到下。橫軸可視區域就是0~viewportWidth。
android:viewportHeight
定義矢量圖形的可視區域的高度。其它見上。[0,0]~[viewportWidth,viewportHeight]定義了虛拟canvas的可視區域。
android:tint
作為染色(tint)的色彩應用到drawable上。預設不應用tint。
android:tintMode
tint顔色的Porter-Duff混合模式,預設是src_in。
android:autoMirrored
如果drawable布局方向是RTL(right-to-left)時,drawable繪制是否需要鏡像化(鏡像化就是繞自身x軸中線旋轉180度)。
android:alpha
drawble的透明度,取值0~1
注意
VectorDrawable記憶體有一個bitmap緩存,如果矢量圖可以确定要用于不同的圖像大小的場景,需要建立多個VectorDrawable,不能複用同一個VectorDrawable,否則會有性能問題。同時,android:width與android:height屬性指定的大小也應該是需要使用的目标大小。
組結點的屬性
組結點定義其包含圖形的轉換資訊(transformation information)。轉換資訊定義在vector指定的視圖區域内(與viewport坐标系相同)。定義的應用轉換的順序是縮放-->旋轉-->平移,是以同時定義的這些屬性最先應用scaleX/scaleY屬性,最後應用translateX/translateY屬性。
屬性名
描述
android:name
定義group的名稱
android:rotation
group對應矢量圖形的旋轉角度,取值是360度制。
android:pivotX
Group旋轉和縮放時的中心點的X軸坐标。取值基于viewport視圖的坐标系,不能使用百分比。
android:pivotY
Group旋轉和縮放時的中心點的Y軸坐标。取值基于viewport視圖的坐标系,不能使用百分比。
android:scaleX
Group在X軸上的縮放比例,最先應用到圖形上。
android:scaleY
Group在Y軸上的縮放比例,最先應用到圖形上。
android:translateX
Group在X軸的平移距離,取值基于viewport視圖的坐标系。最後應用到圖形上。
android:translateY
Group在Y軸的平移距離,取值基于viewport視圖的坐标系。最後應用到圖形上。
路徑結點的屬性
路徑結點定義一個幾何圖形及其填充顔色。
屬性名
描述
android:name
定義路徑的名稱
android:pathData
定義路徑的資料,路徑由多條指令組成,格式與SVG标準的path data的d屬性完全相同,路徑指令的參數定義在viewport視圖的坐标系。具體可以看上面介紹的矢量圖中path的定義與指令
android:fillColor
指定填充路徑的顔色,一般是一個顔色值,在SDK24及以上,可以指定一個顔色狀态清單或者一個漸變的顔色。如果在此屬性上做漸變動畫,新的屬性值會覆寫此值。如果不指定,則path不被填充。
android:strokeColor
指定路徑線條的顔色,一般是一個顔色值,在SDK24及以上,可以指定一個顔色狀态清單或者一個漸變的顔色。如果在此屬性上做漸變動畫,新的屬性值會覆寫此值。如果不指定,則path的線條不會繪制出來。
android:strokeWidth
指定路徑線條的寬度,基于viewport視圖的坐标系(不要dp/px之類的結尾)。
android:strokeAlpha
指定路徑線條的透明度。
android:fillAlpha
指定填充區域的透明度。
android:trimPathStart
取值從0到1,表示路徑從哪裡開始繪制。0~trimPathStart區間的路徑不會被繪制出來。
android:trimPathEnd
取值從0到1,表示路徑繪制到哪裡。trimPathEnd~1區間的路徑不會被繪制出來。
android:trimPathOffset
平移可繪制區域,取值從0到1,線條從(trimPathOffset+trimPathStart繪制到trimPathOffset+trimPathEnd),注意:trimPathOffset+trimPathEnd如果超過1,其實也是繪制的的,繪制的是0~trimPathOffset+trimPathEnd-1的位置。
android:strokeLineCap
設定線條首尾的外觀,三個值:butt(預設,向線條的每個末端添加平直的邊緣), round(向線條的每個末端添加圓形線帽), square(向線條的每個末端添加正方形線帽。)。
android:strokeLineJoin
設定當兩條線條交彙時,建立什麼樣的邊角(線段連接配接類型):三個值:miter(預設,建立尖角),round(建立圓角),bevel(建立斜角) 。
android:strokeMiterLimit
設定設定最大斜接長度,斜接長度指的是在兩條線交彙處内角和外角之間的距離。隻有當 lineJoin 屬性為 "miter" 時,miterLimit 才有效。

android:fillType
設定路徑的填充類型,與SVG格式的fill-rule屬性相同。
裁切路徑結點的屬性
裁切路徑隻能用于目前group和其子元素,隻有在裁切路徑内的元素才會被顯示出來。clip-path定義後才會影響後面path的繪制,如果一個group内有多個path,clip-path定義在第三位,則前面兩個path不受其影響,後面的path受其影響。如果希望clip-path對整個group都生效,應放在第一位。
屬性名
描述
android:name
定義路徑的名稱
android:pathData
定義裁切路徑,取值與pathData相同。
矢量動畫
矢量動畫本質上是屬性動畫系統的一個應用。矢量動畫可以有多種動畫效果:
group屬性的動畫: 對應的旋轉/縮放/平移等效果是傳統的動畫效果。
path屬性的動畫: 對應的屬性可以做出很多絢麗的效果。比如改變pathData屬性,可以做出形狀變化的動畫;改變trimPathStart/trimPathEnd可以做出繪制曲線路徑的效果;改變strokeColor可以做出線條顔色變化的效果。
clip-path屬性的動畫: 對應的pathData屬性可以通過變化做出各種形狀的揭開和遮擋效果。
定義矢量動畫 AnimatedVectorDrawable
第一步:定義矢量圖 VectorDrawable 資源檔案 vd.xml
android:height="64dp"
android:width="64dp"
android:viewportHeight="600"
android:viewportWidth="600" >
android:name="rotationGroup"
android:pivotX="300.0"
android:pivotY="300.0"
android:rotation="45.0" >
android:name="vectorPath"
android:fillColor="#000000"
android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
第二步 : 定義動畫資源檔案 rotation.xml 和 path_morph.xml
旋轉動畫 rotation.xml
android:duration="6000"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="360" />
再定義一個屬性動畫 path_morph.xml
android:duration="3000"
android:propertyName="pathData"
android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z"
android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z"
android:valueType="pathType"/>
該屬性動畫是對屬性名為pathData、屬性值類型為pathType的對象屬性做插值。是以需要兩者具有可比性,valueFrom和valueTo的值内的指令清單必須一一對應(每條指令的參數個數也必須相同),插值工作才能進行。
每次插值的結果,都會被設定到矢量圖形标簽的pathData屬性中,這樣界面重新整理時,矢量圖形指定path繪制的圖案就不斷的重新整理,進而産生動畫效果。
注意:
矢量動畫要求初始幀的路徑指令序列(valueFrom)與結束幀的路徑指令序列(valueTo)内的指令必須一一對應,隻有參數值可以不同,這樣才能插值,進而矢量動畫才能執行。否則編譯後運作時将會崩潰。
第三步:定義矢量動畫 AnimatedVectorDrawable 資源檔案 avd.xml
android:drawable="@drawable/vd" >
android:name="rotationGroup"
android:animation="@anim/rotation" />
android:name="vectorPath"
android:animation="@anim/path_morph" />
可以看出,一個矢量動畫,包含了多個标簽,每個target标簽其實就是對上面定義的矢量圖形的整體或者局部指定動畫效果,如何确定對那塊圖形做動畫,就靠上面定義的矢量圖形塊中定義的名稱(android:name)字段了。對group和path的命名,幫助系統在動畫執行前從矢量圖形内找到它們。
很明顯一個矢量動畫就是在為一個矢量圖的結點綁定定義好的動畫效果。如果覺得像上面那樣一個一個資源檔案來定義比較煩瑣,也可以在一個檔案中完成定義。如下:
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
android:name="root"
android:strokeWidth="2"
android:strokeLineCap="square"
android:strokeColor="?android:colorControlNormal"
android:pathData="M4.8,13.4 L9,17.6 M10.4,16.2 L19.6,7" />
android:propertyName="pathData"
android:valueFrom="M4.8,13.4 L9,17.6 M10.4,16.2 L19.6,7"
android:valueTo="M6.4,6.4 L17.6,17.6 M6.4,17.6 L17.6,6.4"
android:duration="300"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:valueType="pathType" />
引用矢量動畫
矢量動畫定義好之後,便可以如其他圖像資源(Drawable)一樣的方式使用了。然後在Java中,通過取到AnimatedVectorDrawable,執行動畫:
AnimatedVectorDrawable animatedVectorDrawable = (AnimatedVectorDrawable) mImageView.getDrawable();
if(animatedVectorDrawable.isRunning()) {
animatedVectorDrawable.stop();
} else {
animatedVectorDrawable.start();
}
動态更改矢量動畫
矢量圖與矢量動畫的 pathData 都是不可讀寫的,官方的API沒有提供讀寫的接口。這裡介紹一個非常好的工具庫 RichPath。通過這個庫完全控制Path和VectorDrawable的屬性。
相容性問題
// TODO 矢量圖相容方案、矢量動畫相容狀況
參考文章