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)来辅助表达:

举例: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的使用与配置

时间: 2024-07-29 08:40:52

Android动画总结系列(6)——矢量图形与矢量动画的相关文章

Android开发笔记(一百三十二)矢量图形与矢量动画

矢量图形VectorDrawable 与水波图形RippleDrawable一样,矢量图形VectorDrawable也是Android5.0之后新增的图形类.矢量图不同于一般的图形,它是由一系列几何曲线构成的图像,这些曲线以数学上定义的坐标点连接而成.具体到实现上,则需开发者提供一个xml格式的矢量图形定义,然后系统根据矢量定义自动计算该图形的绘制区域.因为绘图结果是动态计算得到,所以不管缩放到多少比例,矢量图形都会一样的清晰,不像位图那样拉大后会变模糊. 矢量图形的xml定义有点复杂,其结构

Silverlight &amp; Blend动画设计系列十一:沿路径动画(Animation Along a Path)

原文:Silverlight & Blend动画设计系列十一:沿路径动画(Animation Along a Path) Silverlight 提供一个好的动画基础,但缺少一种方便的方法沿任意几何路径对象进行动画处理.在Windows Presentation Foundation中提供了动画处理类DoubleAnimationUsingPath和PointAnimationUsingPath,使用这些类就可以非常容易的实现沿几何路径的动画处理,本文提供了基于Silverlight的等效动画类

html打造动画【系列3】- 小猫笑脸动画

猫咪容器 咱们每次画一个图片,肯定先要确定一个容器,几确定一下图形的位置和大小. <div class="mao_box"> <div class="mao"></div> </div> body {margin: 0px;background: #F6F7A7;} .mao_box {position: relative;top: 50px ;} /*设置宽度并且居中显示*/ .mao {margin: 0 auto

Android中矢量动画

Android中用<path> 标签来创建SVG,就好比控制着一支画笔,从一点到一点,动一条线. <path> 标签 支持一下属性 M = (Mx, y) 移动到x,y,并不会划线 L (Lx, y) 直线连到x,y,还有简化命令H(x) 水平连接.V(y)垂直连接 Z,没有参数,连接起点和终点 C=(Cx1, y1, x2, y2, x, y),控制点x1,y1 x2,y2,终点x,y Q=(Qx1, y1, x, y),控制点x1,y1,终点x,y A(Arx, ry, rot

Android自定义组件系列【8】——遮罩文字动画

遮罩文字的动画我们在Flash中非常常见,作为Android的应用开发者你是否也想将这种动画做到你的应用中去呢?这一篇文章我们来看看如何自定义一个ImageView来实现让一张文字图片实现文字的遮罩闪烁效果,下面先来看看效果吧. (录屏幕延时导致效果看起来不是很好) 一.实现原理 实现原理是重写View的onCreate方法,获取图片资源后对每个像素的透明度进行修改来实现,再启动一个线程来循环改变某个区域中的像素透明度. RGBA基础知识:(下面几段介绍文字引用自维基百科) RGBA是代表Red

[转]Android自定义控件三部曲系列完全解析(动画, 绘图, 自定义View)

来源:http://blog.csdn.net/harvic880925/article/details/50995268 一.自定义控件三部曲之动画篇 1.<自定义控件三部曲之动画篇(一)——alpha.scale.translate.rotate.set的xml属性及用法>2.<自定义控件三部曲之动画篇(二)——Interpolator插值器>3.<自定义控件三部曲之动画篇(三)—— 代码生成alpha.scale.translate.rotate.set及插值器动画&g

Android 动画系列之自定义补间动画

转载请标明出处: http://blog.csdn.net/Airsaid/article/details/51591282 本文出自:周游的博客 前言 上一篇写了补间动画的使用,由于篇幅原因,就把自定义补间动画单独拿出来了.这一篇继续写补间动画~ 在上一篇中写到了Android提供了Animation类作为补间动画的抽象基类,并提供了四个子类:ScaleAnimation .TranslateAnimation.AlphaAnimation.RotateAnimation分别实现了四种基本动画

Android性能优化系列之apk瘦身

Android性能优化系列之布局优化 Android性能优化系列之内存优化 为什么APK要瘦身.APK越大,在下载安装过程中,他们耗费的流量会越多,安装等待时间也会越长:对于产品本身,意味着下载转化率会越低(因为竞品中,用户有更多机会选择那个体验最好,功能最多,性能最好,包最小的),所以apk的瘦身优化也很重要,本篇博客将讲述apk瘦身的相关内容. 包体分析 在Android Studio工具栏里,打开build–>Analyze APK, 选择要分析的APK包 可以看到占用空间的主要是代码.图

(android高仿系列)今日头条 --新闻阅读器 (三) 完结 、总结 篇

从写第一篇今日头条高仿系列开始,到现在已经过去了1个多月了,其实大体都做好了,就是迟迟没有放出来,因为我觉得,做这个东西也是有个过程的,我想把这个模仿中一步一步学习的过程,按照自己的思路写下来,在根据碰到的知识点和问题,并且罗列出这些东西的知识点和使用方法.如果你单纯的把做好的一个DEMO拿去改改用用,那样,你永远不知道里面用到的内容是涉及到什么知识点,用什么方法实现,那样就没有多少提升价值而言了. 近期都是在通过开发文档把以前的一些东西重新过一遍,看好多网友都催促想要新版本的,那我就在这里先把