Android动画总结系列(6)——矢量图形与矢量动画

来源:互联网 发布:摄影作品网站 知乎 编辑:程序博客网 时间:2024/05/17 02:49
按照我一开始的打算,上面一篇文章应该是“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)来辅助表达:

举例: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支持的矢量图形。效果如下:


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盗了个图:


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>
效果图:


三、矢量动画

首先要说明一点:矢量动画其实是属性动画系统的一个应用。
矢量动画可以有多种动画效果:
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的子对象,用法都差不多。
效果图:


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" />
效果:


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>
效果还可以,对吧!


四、兼容性问题

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):

这样既能兼容老版本,又能在高版本上(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的使用与配置



1 0
原创粉丝点击