天天看點

Android動畫總結系列(6)——矢量圖形與矢量動畫

按照我一開始的打算,上面一篇文章應該是“Android動畫總結系列(5)——屬性動畫源碼分析”,不過屬性動畫源碼分析寫起來還比較複雜,因為某些原因,我把精力投入到矢量動畫這塊了,第5篇估計會在後面一兩周寫完。本篇文章,我寫的是Android5.0引入的新動畫效果——矢量動畫,初步打算後面還會加一篇源碼分析。

一、概述

1.1 簡述 Android應用的不斷發展帶來了安裝包過大的尴尬,而Android之前一直都不支援矢量圖形,是引起尴尬的一個重要原因。其實Android繪制界面時也是通過各種類似矢量圖形指令操作完成的,是以Android最終在Lolliop引入矢量圖形既是大勢所趨,也是水到渠成的一件事情。矢量圖有很多标準,Android支援的是SVG标準(可縮放矢量圖形Scalable Vector Graphics)。但不是全量支援,準确的說Android支援的是SVG标準中Path相關的部分。 SVG是通用的矢量圖示準,我們可以很輕易的從Photoshop之類的軟體導出想要的圖形。導出的檔案字尾是*.svg,這個檔案的内容是文本格式的,用txt檔案檢視就可以打開,其内部是一系列遵循SVG規範的指令清單。

SVG Path的路徑是一系列的指令組成的,每條指令告訴繪圖系統,如何繪制路徑。指令的寫法: 1)每條指令之間可以用換行/空格/逗号進行分隔; 2)每條指令内的指令和其參數之間可以用換行/空格/逗号分隔,也可以直接連在一起; 3)指令的各個參數間可以用換行/空格/逗号分隔; 通常如果路徑過長,為便于閱讀,建議指令之間用換行,指令與參數之間用空格,參數之間用逗号進行分隔。

1.2 Path指令定義與用法 Android支援的路徑指令包括: M: move to 移動到繪制點 用法:M X,Y (X,Y)是Canvas上的點的位置,M指令會改變Path的初始點 ,如M 10,10或者 M10 10,Path從(10,10)開始繪制。 對應android.graphics.Path的void moveTo(float x, float y)方法。

L:line to 繪制直線 用法:1)L X,Y 從目前位置繪制直線到(X,Y),如L 10,10或者L10 10           2)對于水準方向和垂直方向繪制直線的時候,L指令有兩個簡化表達法,H X表示水準連接配接,V Y表示垂直連接配接,比如M 10,10H12表示的是從(10,10)繪制一條直線到(12,10),M10,10V12表示從(10,10)繪制一條直線到(10,12)。 對應Path類void lineTo(float x, float y)方法。

Z:close 閉合,沒有參數,表示用直線連接配接Path的初始點和Path的終點。 用法:一般Z指令用在一條Path的最末尾,但其實在Z指令後還可以再繼續新的路徑,不過Z指令不改變Path的初始點,是以M2,2L5,5L5,10ZL6,4L7,3Z指令第二個Z認為的Path初始點還是(2,2),如果是M2,2L5,5L5,10ZM3,2L6,4L7,3Z指令,則第二個Z認為的Path起始點是(3,2)。 對應Path類的void close()方法。

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)。 對應Path類的void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)方法,方法參數與上面定義順序完全相同。

S:smooth cubicto繪制一條平滑的三次貝塞爾曲線 用法:S X1,Y1 X,Y 如果前面是一個C/S指令,則自動計算一個保證起始點平滑的對稱控制點(X1',Y1'),從目前點到(X,Y)繪制一條控制點是(X1',Y1')、(X1, Y1)的三次貝塞爾曲線。其它與上面C指令相同。 如果S前面是一個非C/S指令,則無法計算一個對稱的控制點,則從目前點到(X,Y)繪制一條控制點是(X1,Y1)的二次貝塞爾曲線(降階特性)。

Q:quatratic bezier 二次貝塞爾曲線 用法:Q x1,y1 x,y,從目前點到(x,y)繪制一條控制點是(x1,y1)的二次貝塞爾曲線,如Q 6,4 10,5,控制點是(6,4),最終點是(10,5)。 對應Path類的void quadTo(float x1, float y1, float x2, float y2)方法,參數定義與順序同上。

T:smooth quatratic to繪制一條平滑的二次貝塞爾曲線 用法:T X,Y 如果前面是一個Q/T指令,則自動計算一個保證起始點平滑的對稱控制點(X1,Y1),從目前點到(X,Y)繪制一條控制點是(X1,Y1)的二次貝塞爾曲線。其它與上面Q指令相同。 如果T前面是一個非Q/T指令,則無法計算一個對稱的控制點,則從目前點到(X,Y)繪制一條直線(降階特性)。

A:ellipse 繪制一個橢圓圓弧 用法:繪制橢圓圓弧的參數比較複雜,如下:A rx ry x-axis-rotation large-arc-flag sweep-flag X Y,表示繪制一個橢圓圓弧經過(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表示繪制順時針方向的圓弧。 盜個圖(來源http://blog.csdn.net/xu_fu/article/details/44004841)來輔助表達:

Android動畫總結系列(6)——矢量圖形與矢量動畫

舉例:M5,5,A 3,2 0 1 1 5 5.00001 此指令基本上繪制了一個完整的橢圓。注意如果寫成M5,5,A 3,2 0 1 1 5 5就什麼都展示不了了,因為兩點完全相等,指令的目标連接配接兩點已經達到,就不繞大彎子了,是以此處或者目标X或者目标Y要做一點細微的差別。 此指令對應的是void arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo),不過參數需要做轉化。

這裡列出的每個指令還有對應的小寫形式,上面的大寫字母代表後面的參數是絕對坐标,而小寫字母表示相對坐标。 假如目前繪圖位置是X0,Y0,則: M x, y 對應的是 m x-X0, y-Y0;小寫形式對應的是Path類的void rMoveTo(float dx, float dy)。 L x, y 對應的是 l x-X0, y-Y0;小寫形式對應的是Path類的void rLineTo(float dx, float dy)。 Z 對應的是z,兩者作用相同;大小寫形式對應的都是Path類的void close()。 C x1,y1 x2,y2 x,y 對應的是 c x1-X0, y1-Y0, x2-X0, y2-Y0 x-X0, y-Y0;小寫形式對應的是 Path類的void rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)。 Q x1,y1 x,y 對應的是 q x1-X0, y1-Y0 x-X0,y-Y0;小寫形式對應的是Path類的rQuadTo( float dx1, float dy1, float dx2, float dy2)。 A rx ry x-axis-rotation large-arc-flag sweep-flag x y 對應的是 A rx ry x-axis-rotation large-arc-flag sweep-flag x-X0 y-Y0,小寫形式在Path類中與大寫方式對應接口的相同,都需要做參數轉化。

1.3 SVG與Android XML的轉化  http://inloop.github.io/svg2android/提供了将SVG轉化為android的XML檔案的線上工具。 不過要注意一點,因為Android不完全支援SVG标準,是以svg2android的項目會丢棄很多資訊,諸如漸變之類的。 同時在轉化某些非path協定時,轉化效果也有點細節問題。比如轉化svg的ellipse時,它用了一系列的貝塞爾曲線Path來拟合橢圓,更簡化的方法應該是使用A指令來生成橢圓。是以這個工具也不是萬能的,大家不能全靠工具來生成xml。 還要注意,工具生成的xml内width/height屬性直接使用svg的畫布尺寸的,機關是dp,這樣的drawable可能會導緻性能問題,是以要記得改成自己需要的寬高。

二、矢量圖形

2.1 XML定義 矢量圖形對應的XML檔案定義在res/drawable下,在XML檔案中的根标簽是<vector>。 vector支援drawable相關的屬性,如width/height等(必須要設定,否則View是wrap_content時drawable沒寬高無法運作),還支援一些特定屬性,關鍵的有viewportWidth/viewportHeight,這兩個值代表虛拟畫布的寬高,後面的Path繪制裡的參數都是相對此畫布的寬高坐标系進行的。具體的屬性見下面2.2節。

vector标簽下支援0個或多個<group>标簽,表示一組路徑的集合,在<group>内定義一個或多個<path>标簽,定義要被繪制的路徑,也可以不加<group>,直接在<vector>下定義一個或多個<path>标簽,這些path路徑标簽支援fillColor(填充顔色)/pathData(路徑)/strokeWidth/strokeColor等屬性。<group>/<path>标簽下還支援<clip-path>标簽。

如果要繪制的一塊區域,就使用填充顔色fillColor,則路徑起始點到路徑繪制的所有點的都有填充,如果要繪制的是路徑,則使用strokeWidth/strokeColor,不使用fillColor,來繪制路徑。

定義好一個XML後,就可以當正常的drawable資源使用了。下面是一個網上找到的心形路徑的XML定義:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android :width="256dp"
    android :height="256dp"
    android :viewportHeight="32"
    android :viewportWidth="32">

    <path
        android:fillColor= "#8f00"
        android:pathData= "M20.5,9.5
                        c-1.955,0,-3.83,1.268,-4.5,3
                        c-0.67,-1.732,-2.547,-3,-4.5,-3
                        C8.957,9.5,7,11.432,7,14
                        c0,3.53,3.793,6.257,9,11.5
                        c5.207,-5.242,9,-7.97,9,-11.5
                        C25,11.432,23.043,9.5,20.5,9.5z" />
</vector>
           

pathData内的路徑指令清單代表的是一個心形圖案的展示。指令以M指令指定繪制點初始點開始,到z指令封閉路徑結束。pathData内支援上文提到的各種指令。将svg檔案中path部分複制到此處,可以定義一個Android支援的矢量圖形。效果如下:

Android動畫總結系列(6)——矢量圖形與矢量動畫

2.2 特性描述

2.1.1 概述 矢量圖形對應的Java類是VectorDrawable。VectorDrawable沒有提供setPathData之類的方法,是以我們隻能在XML内定義矢量圖形。 為了優化重繪性能,每個VectorDrawable維護了一個Bitmap緩存。是以,使用同一個VectorDrawable的時候,引用的是同一個Bitmap緩存,如果兩個引用位置要求不同的圖像大小,每次大小發生變化的時候,bitmap都需要重新建立并重新繪制,這是一個非常大的開銷。是以,如果一個VectorDrawable在不同的時機需要不同的大小,更高效的方法是建立多個VectorDrawable,這樣每個Size都有一個VectorDrawable,進而減少bitmap操作導緻的開銷。

2.1.2矢量圖形的xml标簽與屬性 矢量圖形的xml檔案支援以下标簽: <vector>:根标簽,表示一個矢量動畫 支援的屬性: 1)android:name:定義矢量圖形的名稱 2)android:width:定義Drawable的寬度,支援所有dimension機關,一般使用dp。drawable的寬度不一定是最終繪制寬度,比如給ImageView設定backgroud則Drawable繪制寬度等于ImageView的寬度,給ImageView設定src則在ImageView大于Drawable寬度時,Drawable繪制寬度等于自己定義的寬度。 3)android:height:定義Drawable的寬度,支援所有dimension機關,一般是dp。其它同上。 4)android:viewportWidth:定義矢量圖形的視圖(viewport)空間的寬度,viewport是一個虛拟的canvas,後面所有的path都在該坐标系上繪制。坐标系左上方為(0,0),橫軸從左向右,縱軸從上到下。橫軸可視區域就是0~viewportWidth。 5)android:viewportHeight:定義矢量圖形的可視區域的高度。其它見上。[0,0]~[viewportWidth,viewportHeight]定義了虛拟canvas的可視區域。 6)android:tint:作為染色(tint)的色彩應用到drawable上。預設不應用tint。 7)android:tintMode:tint顔色的Porter-Duff混合模式,預設是src_in。 8)android:autoMirrored:如果drawable布局方向是RTL(right-to-left)時,drawable繪制是否需要鏡像化(鏡像化就是繞自身x軸中線旋轉180度)。 9)android:alpha:drawble的透明度,取值0~1

<group>:定義一組路徑和子group,另外還定義了轉換資訊(transformation information)。轉換資訊定義在vector指定的視圖區域内(與viewport坐标系相同)。定義的應用轉換的順序是縮放-->旋轉-->平移,是以同時定義的這些屬性最先應用scaleX/scaleY屬性,最後應用translateX/translateY屬性。 支援的屬性: 1)android:name:定義group的名稱 2)android:rotation:group對應矢量圖形的旋轉角度,取值是360度制。 3)android:pivotX:Group旋轉和縮放時的中心點的X軸坐标。取值基于viewport視圖的坐标系,不能使用百分比。 4)android:pivotY:Group旋轉和縮放時的中心點的Y軸坐标。取值基于viewport視圖的坐标系,不能使用百分比。 5)android:scaleX:Group在X軸上的縮放比例,最先應用到圖形上。 6)android:scaleY:Group在Y軸上的縮放比例,最先應用到圖形上。 7)android:translateX:Group在X軸的平移距離,取值基于viewport視圖的坐标系。最後應用到圖形上。 8)android:translateY:Group在Y軸的平移距離,取值基于viewport視圖的坐标系。最後應用到圖形上。

<path>:定義一個路徑,一個路徑即可以表示一塊填充區域也可以表示一根線條。 支援的屬性: 1)android:name:定義路徑的名稱 2)android:pathData:定義路徑的資料,路徑由多條指令組成,格式與SVG标準的path data的d屬性完全相同,路徑指令的參數定義在viewport視圖的坐标系。 3)android:fillColor:指定填充路徑的顔色,一般是一個顔色值,在SDK24及以上,可以指定一個顔色狀态清單或者一個漸變的顔色。如果在此屬性上做漸變動畫,新的屬性值會覆寫此值。如果不指定,則path不被填充。 4)android:strokeColor:指定路徑線條的顔色,一般是一個顔色值,在SDK24及以上,可以指定一個顔色狀态清單或者一個漸變的顔色。如果在此屬性上做漸變動畫,新的屬性值會覆寫此值。如果不指定,則path的線條不會繪制出來。 5)android:strokeWidth:指定路徑線條的寬度,基于viewport視圖的坐标系(不要dp/px之類的結尾)。 6)android:strokeAlpha:指定路徑線條的透明度。 7)android:fillAlpha:指定填充區域的透明度。 8)android:trimPathStart:取值從0到1,表示路徑從哪裡開始繪制。0~trimPathStart區間的路徑不會被繪制出來。 9)android:trimPathEnd:取值從0到1,表示路徑繪制到哪裡。trimPathEnd~1區間的路徑不會被繪制出來。 10)android:trimPathOffset:平移可繪制區域,取值從0到1,線條從(trimPathOffset+trimPathStart繪制到trimPathOffset+trimPathEnd),注意:trimPathOffset+trimPathEnd如果超過1,其實也是繪制的的,繪制的是0~trimPathOffset+trimPathEnd-1的位置。 11)android:strokeLineCap:設定線條首尾的外觀,三個值:butt(預設,向線條的每個末端添加平直的邊緣), round(向線條的每個末端添加圓形線帽), square(向線條的每個末端添加正方形線帽。)。 12)android:strokeLineJoin:設定當兩條線條交彙時,建立什麼樣的邊角(線段連接配接類型):三個值:miter(預設,建立尖角),round(建立圓角),bevel(建立斜角) 。 13)android:strokeMiterLimit:設定設定最大斜接長度,斜接長度指的是在兩條線交彙處内角和外角之間的距離。隻有當 lineJoin 屬性為 "miter" 時,miterLimit 才有效。從http://www.w3school.com.cn/tags/canvas_miterlimit.asp盜了個圖:

Android動畫總結系列(6)——矢量圖形與矢量動畫

14)android:fillType:設定路徑的填充類型,與SVG格式的"fill-rule"屬性相同。見https://www.w3.org/TR/SVG/painting.html#FillRuleProperty。

<clip-path>:定義目前裁切的路徑。裁切路徑隻能用于目前group和其子元素,隻有在裁切路徑内的元素才會被顯示出來。clip-path定義後才會影響後面path的繪制,如果一個group内有多個path,clip-path定義在第三位,則前面兩個path不受其影響,後面的path受其影響。如果希望clip-path對整個group都生效,應放在第一位。 支援的屬性: 1)android:name:定義裁切路徑的名稱 2)android:pathData:定義裁切路徑,取值與上面講的pathData相同。

2.3 一個簡單的笑臉(vector_drawable_smile_face.xml)

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android :width="100dp"
    android :height="100dp"
    android :alpha="2"
    android :viewportHeight="100"
    android :viewportWidth="100">

    <group
        android:name= "test"
        android:pivotX= "50"
        android:pivotY= "50">

        <path
            android:name="face"
            android:fillColor="#ffff00"
            android:pathData="M50,10
                          A40,40 0 1 1 50,90
                          A40,40 0 1 1 50,10" />

        <path
            android:name="left_eye"
            android:fillColor="#000000"
            android:pathData="M30,30
                          A5,5 0 1 1 40,30
                          A5,5 0 1 1 30,30" />

        <path
            android:name="right_eye"
            android:fillColor="#000000"
            android:pathData="M60,30
                          A5,5 0 1 1 70,30
                          A5,5 0 1 1 60,30" />

        <path
            android:name="nose"
            android:pathData="M50,40
                          C55,55 30,58 55,55"
            android:strokeColor="#000000"
            android:strokeWidth="2" />

        <path
            android:name="mouth"
            android:pathData="M35,80
                          Q50,65 65,80"
            android:strokeColor="#000000"
            android:strokeWidth="2" />

    </group >
</vector>
           

效果圖:

Android動畫總結系列(6)——矢量圖形與矢量動畫

三、矢量動畫

首先要說明一點:矢量動畫其實是屬性動畫系統的一個應用。 矢量動畫可以有多種動畫效果: group對應的旋轉/縮放/平移等效果是傳統的動畫效果。 path對應的屬性可以做出很多絢麗的效果。比如改變pathData屬性,可以做出形狀變化的動畫;改變trimPathStart/trimPathEnd可以做出繪制曲線路徑的效果;改變strokeColor可以做出線條顔色變化的效果。 clip-path的pathData變化可以做出各種形狀的揭開和遮擋的效果。

3.1 XML定義 XML定義一個矢量動畫需要完成三部曲: 1)在res/drawable内定義一個矢量圖形 <vector> 2)在res/drawable内定義一個矢量動畫drawable <animated-vector> 3)定義2中使用的屬性動畫 <objectAnimator>

3.1.1 定義矢量圖形 在XML中定義一個矢量動畫,首先需要在res/drawablen内定義一個矢量圖形的XML。下面定義了一個三角形的XML(對應Java類VectorDrawable):

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android :height="200dp"
    android :width="200dp"
    android :viewportHeight="100"
    android :viewportWidth="100" >
    <group
        android:name= "rotateGroup"
        android:pivotX= "50.0"
        android:pivotY= "50.0" >
        <path
            android:name="triangle1"
            android:fillColor="#00ff00"
            android:pathData="M50,30  L 50,30 L 70,70 L 30,70 z" />
        <path
            android:name="triangle2"
            android  :strokeColor="#ff0000"
            android:pathData="M30,80  L 30,80 L 70,80 L 50,90 z" />
    </group >
</vector>
           

我們注意到,上面這個矢量圖内包含一個group标簽,和2個path标簽。group可以包含多個path标簽。這些标簽都做了命名,後面我們需要用标簽的名稱找到矢量圖形内的對象,并對其做動畫。 另外,看pathData部分,我們本來一個三角形的定義隻需要M50,30  L 70,70 L 30,70 z即可,這裡多了一個無意義的L 50,30是幹嘛的呢?這個我們後面再說。

3.1.2 定義矢量動畫 矢量圖形定義好了後,我們就需要指定矢量動畫了。矢量動畫在XML中對應的标簽是<animated-vector>,其XML檔案也定義在res/drawable内,是以本質上也是一個drawable資源,可以在布局中随意使用。矢量動畫可以對上面的矢量圖形的整體或者一部分做動畫效果。也就是對<group>或<path>元素做動畫。 對上圖的整體做效果,其實就是對rotateGroup做動畫,而對部分圖形做效果,我們選用第一個三角形triangle1,讓它過渡成一個矩形。下面是矢量動畫的定義(res/drawable/news_animator_drawable.xml):

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/news_animation_vector" >
    <target
        android:name= "rotateGroup"
        android:animation="@animator/rotate_animator" />
    <target
        android:name= "triangle1"
        android:animation="@animator/path_change" />
</animated-vector >
           

可以看出,一個矢量動畫,包含了多個<target>标簽,每個target标簽其實就是對上面定義的矢量圖形的整體或者局部指定動畫效果,如何确定對那塊圖形做動畫,就靠上面定義的矢量圖形塊中定義的名稱(android:name)字段了。對group和path的命名,幫助系統在動畫執行前從矢量圖形内找到它們。

3.1.3 定義屬性動畫 上面我們講到,矢量動畫是屬性動畫的一個應用。我們可以看到,每個target指定的動畫标簽都是一個屬性動畫。我們來看下這兩個矢量動畫的定義,rotate_animator動畫用于整體矢量圖形旋轉,path_change動畫用于将三角形轉化成四邊形:

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="3000"
    android:propertyName="rotation"
    android:valueFrom="0"
    android:valueTo="360" />
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android :duration="3000"
    android :propertyName="pathData"
    android :valueFrom="M50,30  L 50,30 L 70,70 L 30,70 z"
    android :valueTo="M30,30 L 70,30 L 70,70 L 30,70 z"
    android :valueType="pathType" />
           

上面一個屬性動畫我們很熟悉了,以前講的時候這種旋轉都是應用在View上,這次是應用在矢量圖形的group上,這說明矢量圖形的group标簽對應的java類有類似setRotation()之類的接口做圖形旋轉 。

下面這個屬性動畫我們比較陌生,不過其本質還是屬性動畫對類型為pathType的對象屬性值做插值。既然是插值,我們就需要兩者具有可比性,是以valueFrom和valueTo的值内的指令清單必須一一對應(每條指令的參數個數也必須相同),插值工作才能進行,這也就是上文中我們定義了一個無意義的L50,30指令的價值所在。 每次插值的結果,都會被設定到矢量圖形<path>标簽的pathData屬性中,這樣界面重新整理時,矢量圖形指定path繪制的圖案就不斷的重新整理,進而産生動畫效果。 注意:再強調一遍,矢量動畫要求初始幀的路徑指令序列(valueFrom)與結束幀的路徑指令序列(valueTo)内的指令必須一一對應,隻有參數值可以不同,這樣才能插值,進而矢量動畫才能執行。否則編譯後運作時就崩潰了。

3.1.4 內建運作動畫 這時候,矢量動畫已經定義好了,怎麼把它內建到View内執行動畫呢? 1)首先給一個ImageView定義src為剛剛定義的矢量動畫drawable:

<ImageView
    android :id="@+id/imageview"
    android :layout_width="wrap_content"
    android :layout_height="wrap_content"
    android:src="@drawable/news_animator_drawable"
    android :layout_marginTop="10dp"
    android :background="#88aa88"/>
           

2)在Java代碼内,通過取到AnimatedVectorDrawable,執行動畫:

AnimatedVectorDrawable animatedVectorDrawable =
        (AnimatedVectorDrawable) mImageView.getDrawable();
if(animatedVectorDrawable.isRunning()) {
    animatedVectorDrawable.stop();
} else {
    animatedVectorDrawable.start();
}
           

然後一個矢量動畫就運作起來喽!這段代碼是不是和幀動畫的啟動完全一緻,對吧!因為它們都是Drawable的子對象,用法都差不多。 效果圖:

Android動畫總結系列(6)——矢量圖形與矢量動畫

3.1.5 給笑臉做個動畫

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android :drawable="@drawable/smile_face">
    <target
        android:animation="@animator/anim_smile_left_eye"
        android:name= "left_eye"/>

    <target
        android:animation="@animator/anim_smile_nose"
        android:name= "nose"/>

    <target
        android:animation="@animator/anim_smile_mouth"
        android:name= "mouth"/>
</animated-vector >
           
<!--anim_smile_left_eye.xml-->
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android :duration="1000"
    android :propertyName="pathData"
    android :valueFrom="M30,30 A5 ,5  0 1 1 40,30 A5 ,5  0 1 1 30,30"
    android :valueTo="  M34,30 A1 ,1  0 1 1 36,30 A1 ,1  0 1 1 34,30"
    android :valueType="pathType" />
           
<!--anim_smile_mouth.xml-->
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android :duration="1000"
    android :propertyName="pathData"
    android :valueFrom="M35,80 Q50,65 65,80"
    android :valueTo="M35,80 Q50,90 65,80"
    android :valueType="pathType" />
           
<!--anim_smile_nose.xml-->
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android :duration="1000"
    android :propertyName="trimPathEnd"
    android :valueFrom="0.2"
    android :valueTo="1"
    android :valueType="floatType" />
           

效果:

Android動畫總結系列(6)——矢量圖形與矢量動畫

3.2 路徑繪制過程動畫 矢量圖形的path标簽繪制時,存在兩個屬性trimPathStart、trimPathEnd,對這兩個屬性做屬性動畫可以得到路徑軌迹不斷繪制的效果,以第2個三角形triangle2為變化對象: 在矢量動畫定義的animated-vector中加入:

<target
    android :name="triangle2"
    android:animation="@animator/trimpathend_change" />
           

定義一個新的屬性動畫trimpathend_change.xml:

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android :duration="3000"
    android :propertyName="trimPathEnd"
    android :valueFrom="0"
    android :valueTo="1"
    android :valueType="floatType" />
           

動畫執行後,path的trimPathEnd屬性從0變化到1的過程就是路徑不斷繪制出來的過程。

這裡要解釋下什麼是trimPathStart/trimPathEnd: trimPathStart:開始路徑的百分比,取值在0~1,0表示從路徑開始位置繪制,整個路徑都可見,1表示路徑完全不繪制,整個路徑不可見; trimPathEnd:結束路徑的百分比,取值在0~1,0表示繪制到路徑開始位置就不繪制,其實就是路徑不繪制,不可見,1表示繪制到路徑結束位置,是以整個路徑完全可見; 将路徑的長度歸一化,則一個Path繪制的可見區域應該是[trimPathStart,trimPathEnd]。

既然原理已經說清楚了,那麼我們來看個稍微複雜點的例子,大家肯定看到過一種系統自帶的轉圈動畫,箭頭轉圈的過程中,它後面已經繪制的圓弧不斷消失,最終一圈跑下來,又歸于原位。這種效果就可以用trimPathStart和trimPathEnd實作。trimPathStart是路徑開始繪制的位置,trimPathEnd是路徑結束繪制的位置。是以如果這兩個屬性都發生改變,但是trimPathStart抹去路徑繪制區域的速度慢于trimPathEnd的時候會怎麼樣呢?是不是就造成了這種轉圈效果呢?下面我就不繪制圓了,用上面的三角形triangle2做例子(res/animator/trimstartend_change.xml):

<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <objectAnimator
        android:duration= "3000"
        android:propertyName= "trimPathStart"
        android:valueFrom= "0"
        android:valueTo= "1"
        android:valueType= "floatType" />

    <objectAnimator
        android:duration= "2000"
        android:propertyName= "trimPathEnd"
        android:valueFrom= "0"
        android:valueTo= "1"
        android:valueType= "floatType" />
</set>
           

效果還可以,對吧!

Android動畫總結系列(6)——矢量圖形與矢量動畫

四、相容性問題

4.1 VectorDrawable的png生成政策 釋出于Android 5.0的VectorDrawable,有一段時間官方是不支援低版本的。 Android build tools提供一種方案,如果編譯版本是5.0以下版本,則會把VectorDrawable生成對應的png圖檔,這樣在5.0以下的版本則使用的是生成的png圖,而在5.0以上的版本中則使用VectorDrawable。 大家知道,Android有多個螢幕密度(ldpi/mdpi/hdpi/xhdpi/xxhdpi....),每個都生成一個一張png,那還不如一開始就切位圖呢!是以buid toos提供一個配置(build.gradle):

defaultConfig {
    applicationId "org.qcode.androidsvgdemo"
    minSdkVersion 9
    targetSdkVersion 23
    versionCode 1
    versionName "1.0"
    generatedDensities = [ 'hdpi', 'xhdpi' ]
}
           

minSdkVersion支援到9,此時generatedDensities生效(注意:如果minSdkVersion 21,則此屬性不生效,直接使用矢量圖xml)。generatedDensities = [ 'hdpi', 'xhdpi' ]表示隻給hdpi和xhdpi生成png圖檔。其他密度都不生成(此屬性不配置,就對所有螢幕密度都生成一份png):

Android動畫總結系列(6)——矢量圖形與矢量動畫

這樣既能相容老版本,又能在高版本上(drawable-anydpi-v21)上使用矢量圖形。 這裡還要注意另一個問題,正常情況下,我們可以通過@string/**來引用pathData,但如果生成png,則使用@string/**會報錯,此時pathData的内容隻能寫在矢量圖形的xml檔案内。

4.2 AnimatedVectorDrawable不相容的解決 前面說了通過png生成來支援VectorDrawable在低版本的展示,但是AnimatedVectorDrawable沒辦法通過這種方式支援,是以在使用矢量動畫時需要注意:如果不考慮支援5.0之前的版本,則一切OK。否則應把矢量圖形資源放到 res/drawable目錄中,把矢量動畫放到 drawable-v21 目錄中,并在drawable 中提供一個和 AnimatedVectorDrawable同名字的資源來在 5.0之前的版本使用(這個資源可以考慮使用selector來做點效果)。

4.3 開發者社群的支援 https://github.com/trello/victor https://github.com/telly/MrVector https://github.com/wnafee/vector-compat vector-compat相對比較好,不過後面google提供了官方支援,這些支援可以不用看了。

4.4 官方低版本支援 Android最終釋出了官方Support包(support-vector-drawable)的VectorDrawableCompat做低版本相容(最低支援到API 7)。是以如果我們使用VectorDrawableCompat加載矢量資源,就不需要再生成png了。 要在工程中支援低版本的矢量圖形和動畫,需要support0vector-drawable庫和23.2.0+的appcompat-v7庫(還要取消png生成,支援于android studio1.4) 。 compile 'com.android.support:appcompat-v7:23.2.0'編譯出support-vector-drawable-23.2.0和animated-vector-drawable-23.2.0這兩個庫。 工程配置方面,VectorDrawableCompat需要依賴aapt的一些功能,來保持最近矢量圖使用的添加的屬性ID,以便他們可以被v21之前的引用。想要的在build.gradle需要增加一些配置: 如果Gradle插件版本V2.0及以上,則需要加入:

android {
    defaultConfig {
       vectorDrawables.useSupportLibrary = true
    }
}
           

如果Gradle插件版本在V1.5及以下,則需要添加:

android {
     defaultConfig {
         //不生成png
         generatedDensities = []
     }

    aaptOptions {
         additionalParameters "--no-version-vectors"
    }
}
           

內建時需要注意: 1)使用android:src屬性的地方需要替換為app:srcCompat屬性。 2)在非src屬性的地方使用矢量圖時,需要将矢量圖用drawable容器(如StateListDrawable, InsetDrawable, LayerDrawable, LevelListDrawable, 和RotateDrawable)包裹起來使用。否則會在低版本的情況下報錯。 詳細的說明可以參考:http://www.tuicool.com/articles/3emUnmM

五、總結

本文總結了矢量圖形和矢量動畫相關的知識。下面再分析下Android5.0引入矢量圖形帶來的改變: 矢量圖形帶來的好處: 1)無限拉伸不失真,免去多個螢幕密度下內建多套切片的問題,減少安裝包體積 2)帶來了變形動畫的動畫方式(矢量變形動畫) 3)帶來了複雜路徑繪制的動畫方式(矢量路徑繪制動畫)

矢量圖形存在的缺陷: 1)相容性問題:5.0以下版本的相容性。 2)不完全支援SVG标準,SVG與VectorDrawable沒有可比性。我們不能直接在View上展示svg格式的圖檔。VectorDrawable 支援SVG的一部分規則(主要是SVG中定義path部分的規則 ),我們基本上隻能将svg中的Path定義的資料用在VectorDrawable的pathData中(其它标簽需要工具轉化成path)。 3)VectorDrawable記憶體有一個bitmap緩存,如果矢量圖可以确定要用于不同的圖像大小的場景,需要建立多個VectorDrawable,不能複用同一個VectorDrawable,否則會有性能問題。

問題雖然多多,但是矢量圖形和矢量動畫帶來的好處是不言而喻的,它們極大的豐富了屬性動畫的應用場景,Android5.0後系統的動畫越來越絢麗,很大程度上都與此相關。在Android應用爆炸發展的今天,精緻的動畫效果已經成了應用拉使用者的一個很重要的方式,相信矢量動畫的應用場景會越來越豐富。

參考文檔: 1)http://blog.csdn.net/xu_fu/article/details/44004841 2)http://www.w3.org/TR/SVG11/paths.html#PathData 3)http://blog.csdn.net/ljx19900116/article/details/41806875 4)http://www.shejidaren.com/8000-flat-icons.html 已設計好的SVG圖示 5)http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0123/2346.html 6)http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0201/2396.html 7)http://mobile.51cto.com/news-478709.htm 8)http://www.w3cplus.com/css3/css-svg-clipping.html裁切的作用 9)http://blog.csdn.net/u013394527/article/details/50747753 VectorDrawableCompat相容5.0以下版本 10)http://www.tuicool.com/articles/3emUnmMVectorDrawableCompat的使用與配置