SVG,即Scalable Vector Graphics 可伸缩矢量图形,这种图像格式在前端中已经使用的非常广泛了。
首先要解释下什么是矢量图像,什么是位图图像?
1、矢量图像:SVG是W3C 推出的一种开放标准的文本式矢量图形描述语言,他是基于XML的、专门为网络而设计的图像格式,SVG是一种采用XML来描述二维图形的语言,所以它可以直接打开xml文件来修改和编辑。
2、位图图像:位图图像的存储单位是图像上每一点的像素值,因而文件会比较大,像GIF、JPEG、PNG等都是位图图像格式。
Vector,在Android中指的是VectorDrawable,也就是Android中的矢量图,可以说Vector就是Android中的SVG实现(并不是支持全部的SVG语法),Vector图像刚发布的时候,是只支持Android 5.0+的,自从AppCompat 23.2之后,Vector可以使用于Android 2.1以上的所有系统,只需要引用com.android.support:appcompat-v7:23.2.0以上的版本就可以了。(所谓的兼容即低版本非真实使用SVG,而是生成PNG图片)

Vector Drawable相对于普通的Drawable来说,有以下几个好处:
(1)Vector图像可以自动进行适配,不需要通过分辨率来设置不同的图片。
(2)Vector图像可以大幅减少图像的体积,同样一张图,用Vector来实现,可能只有PNG的几十分之一。
(3)使用简单,很多设计工具,都可以直接导出SVG图像,从而转换成Vector图像 功能强大。
(4)不用写很多代码就可以实现非常复杂的动画 成熟、稳定,前端已经非常广泛的进行使用了。

Vector 语法简介
通过使用它的Path标签,几乎可以实现SVG中的其它所有标签,虽然可能会复杂一点,但这些东西都是可以通过工具来完成的,所以,不用担心写起来会很复杂。
Path标签中的android:pathData数据则是由以下指令组成的:
M = moveto(M X,Y) :将画笔移动到指定的坐标位置,相当于 android Path 里的moveTo(),只是移动了画笔, 没有画任何东西。
L = lineto(L X,Y) :画直线到指定的坐标位置,相当于 android Path 里的lineTo()
H = horizontal lineto(H X):画水平线到指定的X坐标位置
V = vertical lineto(V Y):画垂直线到指定的Y坐标位置
C = curveto(C X1,Y1,X2,Y2,ENDX,ENDY):三阶贝塞尔曲线
S = smooth curveto(S X2,Y2,ENDX,ENDY) 同三阶贝塞尔曲线,更平滑
Q = quadratic Belzier curve(Q X,Y,ENDX,ENDY):二阶贝塞尔曲线
T = smooth quadratic Belzier curveto(T ENDX,ENDY):映射 同样二阶贝塞尔曲线,更平滑
A = elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧线 ,相当于arcTo()
Z = closepath():关闭路径(会自动绘制链接起点和终点)
注意:关于这些语法,开发者不需要全部精通,而是能够看懂即可,这些path标签及数据生成都可以交给工具来实现。
(一般美工来帮你搞定!PS、Illustrator等等都支持导出SVG图片);没必要去学习使用这些设计工具,开发者可以利用一些工具,自己转换一些比较基础的图像,可以先用http://editor.method.ac/ 生成SVG图片,然后用http://inloop.github.io/svg2android/ 生成 VectorDrawable xml代码;我们也可以使用Android studio生成SVG,打开一个项目,在Android视图中,右击res文件夹,选择New->Vector Asset

Android中的VectorDrawable的xml代码如下:

<vector xmlns:android="http://schemas.android.com/apk/res/android"android:width="24dp"android:height="24dp"android:viewportWidth="24.0"android:viewportHeight="24.0"><pathandroid:fillColor="#FF000000"android:pathData="M6,18c0,0.55 0.45,1 1,1h1v3.5c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5L11,19h2v3.5c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5L16,19h1c0.55,0 1,-0.45 1,-1L18,8L6,8v10zM3.5,8C2.67,8 2,8.67 2,9.5v7c0,0.83 0.67,1.5 1.5,1.5S5,17.33 5,16.5v-7C5,8.67 4.33,8 3.5,8zM20.5,8c-0.83,0 -1.5,0.67 -1.5,1.5v7c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5v-7c0,-0.83 -0.67,-1.5 -1.5,-1.5zM15.53,2.16l1.3,-1.3c0.2,-0.2 0.2,-0.51 0,-0.71 -0.2,-0.2 -0.51,-0.2 -0.71,0l-1.48,1.48C13.85,1.23 12.95,1 12,1c-0.96,0 -1.86,0.23 -2.66,0.63L7.85,0.15c-0.2,-0.2 -0.51,-0.2 -0.71,0 -0.2,0.2 -0.2,0.51 0,0.71l1.31,1.31C6.97,3.26 6,5.01 6,7h12c0,-1.99 -0.97,-3.75 -2.47,-4.84zM10,5L9,5L9,4h1v1zM15,5h-1L14,4h1v1z"/>
</vector>

vector标签是用来定义这个矢量图的,该元素包含如下属性:
android:name 定义该drawable的名字
android:width 定义该 drawable 的内部(intrinsic)宽度,支持所有 Android 系统支持的尺寸,通常使用 dp
android:height 定义该 drawable 的内部(intrinsic)高度,支持所有 Android 系统支持的尺寸,通常使用 dp
android:viewportWidth 定义矢量图视图的宽度,视图就是矢量图 path 路径数据所绘制的虚拟画布
android:viewportHeight 定义矢量图视图的高度,视图就是矢量图 path 路径数据所绘制的虚拟画布
android:tint 定义该 drawable 的 tint 颜色。默认是没有 tint 颜色的
android:tintMode 定义 tint 颜色的 Porter-Duff blending 模式,默认值为 src_in
android:autoMirrored 设置当系统为 RTL (right-to-left) 布局的时候,是否自动镜像该图片。比如 阿拉伯语。
android:alpha 该图片的透明度属性

path标签中的pathData就是矢量图的路径数据,除此之外还可以设置其他属性。 path 元素一共包含如下属性:
android:name 定义该 path 的名字,这样在其他地方可以通过名字来引用这个路径
android:pathData 和 SVG 中 d 元素一样的路径信息。
android:fillColor 定义填充路径的颜色,如果没有定义则不填充路径
android:strokeColor 定义如何绘制路径边框,如果没有定义则不显示边框
android:strokeWidth 定义路径边框的粗细尺寸
android:strokeAlpha 定义路径边框的透明度
android:fillAlpha 定义填充路径颜色的透明度
android:trimPathStart 从路径起始位置截断路径的比率,取值范围从 0 到1
android:trimPathEnd 从路径结束位置截断路径的比率,取值范围从 0 到1
android:trimPathOffset 设置路径截取的范围 Shift trim region (allows showed region to include the start and end), in the range from 0 to 1.
android:strokeLineCap 设置路径线帽的形状,取值为 butt, round, square.
android:strokeLineJoin 设置路径交界处的连接方式,取值为 miter,round,bevel.
android:strokeMiterLimit 设置斜角的上限,Sets the Miter limit for a stroked path. 注:当strokeLineJoin设置为 “miter” 的时候, 绘制两条线段以锐角相交的时候,所得的斜面可能相当长。当斜面太长,就会变得不协调。strokeMiterLimit 属性为斜面的长度设置一个上限。这个属性表示斜面长度和线条长度的比值。默认是 10,意味着一个斜面的长度不应该超过线条宽度的 10 倍。如果斜面达到这个长度,它就变成斜角了。当 strokeLineJoin 为 “round” 或 “bevel” 的时候,这个属性无效。

group标签可以把多个 path 放到一起,group 主要是用来设置路径做动画的关键属性的。 group 支持的属性如下:
android:name 定义 group 的名字
android:rotation 定义该 group 的路径旋转多少度
android:pivotX 定义缩放和旋转该 group 时候的 X 参考点。该值相对于 vector 的 viewport 值来指定的。
android:pivotY 定义缩放和旋转该 group 时候的 Y 参考点。该值相对于 vector 的 viewport 值来指定的。
android:scaleX 定义 X 轴的缩放倍数
android:scaleY 定义 Y 轴的缩放倍数
android:translateX 定义移动 X 轴的位移。相对于 vector 的 viewport 值来指定的。
android:translateY 定义移动 Y 轴的位移。相对于 vector 的 viewport 值来指定的。

clip-path标签定义当前绘制的剪切路径。注意,clip-path 只对当前的 group 和子 group 有效,属性如下:
android:name 定义 clip path 的名字
android:pathData

相关兼容问题
兼容5.0以下的版本:使用Android Studio 2.2以上的版本,gradle版本在2.0以上

1.添加defaultConfig {vectorDrawables.useSupportLibrary = true}
2.添加compile 'com.android.support:appcompat-v7:25.3.1' //需要是23.2 版本以上的
3.Activity需要继承与AppCompatActivity
4.使用在Actvity前面添加一个flag设置static {AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);}
5.Button 不支持app:srcCompatXml 使用在Button的selectorRadioButton 直接使用textview的drawable  直接使用使用的动态Vector Drawable主要是不能直接修改 pathData不能使用自定义interpolator

实例(一)使用svg实现点击打勾效果

实现这个动画效果主要是通过修改VectorDrawable中Path的android:trimPathEnd属性来实现
这个属性android:trimPathEnd 从路径结束位置截断路径的比率,取值范围从 0 到1,表示是从最开始到当前值结束的路径
1.首先获取勾的svg图片xml文件,给Path命名为gou,方便做动画使用

<vector xmlns:android="http://schemas.android.com/apk/res/android"android:width="24dp"android:height="24dp"android:viewportWidth="24.0"android:viewportHeight="24.0"><pathandroid:name="gou"android:fillColor="#FF000000"android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>

2.添加AnimatedVectorDrawable对应的xml文件
animated-vector标签中指定执行动画的svg资源文件
target标签表示动画执行对象name为gou的Path,执行动画为check_anim

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"android:drawable="@drawable/ic_check"><targetandroid:animation="@animator/check_anim"android:name="gou"/>
</animated-vector>

3.添加自定义动画文件
这里使用了属性动画objectAnimator,对Path的trimPathEnd这个属性值进行修改,修改范围为0.0f-1.0f

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"><objectAnimatorandroid:duration="500"android:propertyName="trimPathEnd"android:valueFrom="0"android:valueTo="1"android:valueType="floatType"/>
</set>

4.到这里所有动态Vector Drawable相关配置完成,接下来在控件中使用

<ImageViewandroid:layout_marginTop="30dp"android:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="startAnim"android:src="@drawable/check_anim"/>

5.要实现动画效果,这样还不行还需要手动播放动画

public void startAnim(View view){ImageView imageView= (ImageView) view;Drawable drawable=imageView.getDrawable();Animatable animatable= (Animatable) drawable;animatable.start();}

(二)实现搜索框动画效果

实现此动画的关键在于修改Path中的trimPathStart属性来实现

1.生成VectorDrawable文件searchbar.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"android:width="150dp"android:height="24dp"android:viewportHeight="24"android:viewportWidth="150"><pathandroid:name="search"android:pathData="M141,17 A9,9 0 1,1 142,16 L149,23"android:strokeAlpha="0.8"android:strokeColor="#000000"android:strokeLineCap="round"android:strokeWidth="2"/><pathandroid:name="bar"android:pathData="M0,23 L149,23"android:strokeAlpha="0.8"android:strokeColor="#000000"android:strokeLineCap="square"android:strokeWidth="2"/>
</vector>

2.添加AnimatedVectorDrawable文件

<animated-vectorxmlns:android="http://schemas.android.com/apk/res/android"android:drawable="@drawable/searchbar"><targetandroid:name="search"android:animation="@animator/anim_searchbar_in"/><targetandroid:name="bar"android:animation="@animator/anim_searchbar_out"/></animated-vector>

3.根据实现的效果来写动画文件
anim_searchbar_in.xml

<objectAnimatorxmlns:android="http://schemas.android.com/apk/res/android"android:duration="1000"android:propertyName="trimPathStart"android:repeatCount="infinite"android:repeatMode="reverse"android:valueFrom="0"android:valueTo="1"android:valueType="floatType"/>

anim_searchbar_out.xml

<objectAnimatorxmlns:android="http://schemas.android.com/apk/res/android"android:duration="1000"android:propertyName="trimPathStart"android:repeatCount="infinite"android:repeatMode="reverse"android:valueFrom="1"android:valueTo="0"android:valueType="floatType"/>

4.代码中开启动画效果

public void startAnim(View view){ImageView imageView= (ImageView) view;Drawable drawable=imageView.getDrawable();Animatable animatable= (Animatable) drawable;animatable.start();}

(三)实现绘制图形的效果

实现此动画的关键在于修改Path中的trimPathEnd和strokeColor属性来实现,group用于控制当前path的缩放
1.添加VectorDrawable文件star.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"android:width="200dp"android:height="200dp"android:viewportHeight="500"android:viewportWidth="500"><groupandroid:scaleX="5.0"android:scaleY="5.0"><pathandroid:name="star"android:pathData="M 50.0,90.0 L 82.9193546357,27.2774101308 L 12.5993502926,35.8158045183 L 59.5726265715,88.837672697 L 76.5249063296,20.0595700732 L 10.2916450361,45.1785327898 L 68.5889268818,85.4182410261 L 68.5889268818,14.5817589739 L 10.2916450361,54.8214672102 L 76.5249063296,79.9404299268 L 59.5726265715,11.162327303 L 12.5993502926,64.1841954817 L 82.9193546357,72.7225898692 L 50.0,10.0 L 17.0806453643,72.7225898692 L 87.4006497074,64.1841954817 L 40.4273734285,11.162327303 L 23.4750936704,79.9404299268 L 89.7083549639,54.8214672102 L 31.4110731182,14.5817589739 L 31.4110731182,85.4182410261 L 89.7083549639,45.1785327898 L 23.4750936704,20.0595700732 L 40.4273734285,88.837672697 L 87.4006497074,35.8158045183 L 17.0806453643,27.2774101308 L 50.0,90.0Z"android:strokeColor="#000000"android:strokeWidth="2"/></group>
</vector>

2.添加AnimatedVectorDrawable文件

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"android:drawable="@drawable/star"><targetandroid:name="star"android:animation="@animator/anim_star" /></animated-vector>

3.根据实现的效果来编写动画文件anim_star.xml

<set xmlns:android="http://schemas.android.com/apk/res/android"><objectAnimatorandroid:duration="5000"android:interpolator="@android:interpolator/linear"android:propertyName="trimPathEnd"android:repeatCount="infinite"android:repeatMode="restart"android:valueFrom="0"android:valueTo="1"android:valueType="floatType"/><objectAnimatorandroid:duration="5000"android:propertyName="strokeColor"android:repeatCount="infinite"android:repeatMode="restart"android:valueFrom="@color/colorAccent"android:valueTo="@color/colorPrimaryDark"/></set>

4.开始执行播放动画

public void startAnim(View view){ImageView imageView= (ImageView) view;Drawable drawable=imageView.getDrawable();Animatable animatable= (Animatable) drawable;animatable.start();}

(四)实现两个箭头左右平移的动画

实现该效果主要通过修改Path中的translateX属性来实现

1.添加VectorDrawable文件ic_arrow.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"android:width="120dp"android:height="120dp"android:viewportHeight="24.0"android:viewportWidth="24.0"><group android:name="left"><pathandroid:fillColor="#FF000000"android:pathData="M9.01,14L2,14v2h7.01v3L13,15l-3.99,-4v3"/></group><group android:name="right"><pathandroid:fillColor="#FF000000"android:pathData="M14.99,13v-3L22,10L22,8h-7.01L14.99,5L11,9l3.99,4"/></group></vector>

2.添加AnimatedVectorDrawable文件

<animated-vectorxmlns:android="http://schemas.android.com/apk/res/android"android:drawable="@drawable/ic_arrow"><targetandroid:name="left"android:animation="@animator/anim_left"/><targetandroid:name="right"android:animation="@animator/anim_right"/></animated-vector>

3.根据实现的效果来编写动画文件anim_left.xml,anim_right.xml

<objectAnimatorxmlns:android="http://schemas.android.com/apk/res/android"android:duration="1000"android:interpolator="@android:interpolator/anticipate_overshoot"android:propertyName="translateX"android:repeatCount="infinite"android:repeatMode="reverse"android:valueFrom="0"android:valueTo="-10"android:valueType="floatType"/>
<objectAnimatorxmlns:android="http://schemas.android.com/apk/res/android"android:duration="1000"android:interpolator="@android:interpolator/anticipate_overshoot"android:propertyName="translateX"android:repeatCount="infinite"android:repeatMode="reverse"android:valueFrom="0"android:valueTo="10"android:valueType="floatType"/>

4.开始执行播放动画

public void startAnim(View view){ImageView imageView= (ImageView) view;Drawable drawable=imageView.getDrawable();Animatable animatable= (Animatable) drawable;animatable.start();}

(五)实现一个颜色不断变化的矩形

实现该效果主要通过修改Path中的fillColor属性来实现

1.添加VectorDrawable文件square.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"android:width="200dp"android:height="200dp"android:viewportHeight="500"android:viewportWidth="500"><pathandroid:name="square"android:fillColor="#000000"android:pathData="M100,100 L400,100 L400,400 L100,400 z"/></vector>

2.添加AnimatedVectorDrawable文件

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"android:drawable="@drawable/square"><targetandroid:name="square"android:animation="@animator/anim_square_color" /></animated-vector>

3.根据实现的效果来编写动画文件anim_square_color.xml

<objectAnimatorxmlns:android="http://schemas.android.com/apk/res/android"android:duration="1000"android:interpolator="@android:interpolator/decelerate_cubic"android:propertyName="fillColor"android:repeatCount="infinite"android:repeatMode="reverse"android:valueFrom="@android:color/holo_green_dark"android:valueTo="@android:color/darker_gray"android:valueType="intType"/>

4.开始执行播放动画

public void startAnim(View view){ImageView imageView= (ImageView) view;Drawable drawable=imageView.getDrawable();Animatable animatable= (Animatable) drawable;animatable.start();}

(六) 也可以通过VectorDawable来实现一个选择器
可以将VectorDrawable当成一个Dawable资源来使用 VectorDrawable extends Drawable

1.添加VectorDrawable文件selector1.xml,selector2.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"android:width="24dp"android:height="24dp"android:viewportHeight="24.0"android:viewportWidth="24.0"><pathandroid:fillColor="#FF000000"android:pathData="M14.59,8L12,10.59 9.41,8 8,9.41 10.59,12 8,14.59 9.41,16 12,13.41 14.59,16 16,14.59 13.41,12 16,9.41 14.59,8zM12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android"android:width="24dp"android:height="24dp"android:viewportHeight="24.0"android:viewportWidth="24.0"><pathandroid:fillColor="#FF000000"android:pathData="M11,15h2v2h-2zM11,7h2v6h-2zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
</vector>

2.添加选择器selector

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:drawable="@drawable/selector1" android:state_pressed="true"/><item android:drawable="@drawable/selector2"/>
</selector>

3.直接在布局中引用资源即可

(七)实现一个将五星变成一个梯形的效果

实现此动画主要是通过修改Path中的pathData属性来实现,
注意:valueFrom与valueTo中的值需要一 一对应起来,不然会出错

1.添加VectorDrawable文件fivestar.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"android:width="120dp"android:height="120dp"android:viewportHeight="64"android:viewportWidth="64"><group><pathandroid:name="star"android:fillColor="#ff0000"android:pathData="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z"android:strokeColor="#000000"android:strokeWidth="1"/></group>
</vector>

2.添加AnimatedVectorDrawable文件

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"android:drawable="@drawable/fivestar"><targetandroid:name="star"android:animation="@animator/anim_fivestar_morph" />
</animated-vector>

3.根据实现的效果来编写动画文件anim_fivestar_morph.xml

<objectAnimatorxmlns:android="http://schemas.android.com/apk/res/android"android:duration="1000"android:propertyName="pathData"android:valueFrom="M 48,54 L 31,42 15,54 21,35 6,23 25,23 32,4 40,23 58,23 42,35 z"android:valueTo="M 48,54 L 31,54 15,54 10,35 6,23 25,10 32,4 40,10 58,23 54,35 z"android:valueType="pathType"/>

4.开始执行播放动画

(八)实现百度图标加载动画效果

实现此动画主要是通过修改Path中的pathData属性来实现,
注意:valueFrom与valueTo中的值需要一 一对应起来,不然会出错

1.添加VectorDrawable文件vd_path_paw.xml

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"android:width="120dp"android:height="120dp"android:viewportHeight="24"android:viewportWidth="24"><pathandroid:name="toe1"android:fillColor="#ffffff"android:pathData="M 4.5 7 C 5.88071187458 7 7 8.11928812542 7 9.5 C 7 10.8807118746 5.88071187458 12 4.5 12 C 3.11928812542 12 2 10.8807118746 2 9.5 C 2 8.11928812542 3.11928812542 7 4.5 7 Z" /><pathandroid:name="toe2"android:fillColor="#ffffff"android:pathData="M 9 3 C 10.3807118746 3 11.5 4.11928812542 11.5 5.5 C 11.5 6.88071187458 10.3807118746 8 9 8 C 7.61928812542 8 6.5 6.88071187458 6.5 5.5 C 6.5 4.11928812542 7.61928812542 3 9 3 Z" /><pathandroid:name="toe3"android:fillColor="#ffffff"android:pathData="M 15 3 C 16.3807118746 3 17.5 4.11928812542 17.5 5.5 C 17.5 6.88071187458 16.3807118746 8 15 8 C 13.6192881254 8 12.5 6.88071187458 12.5 5.5 C 12.5 4.11928812542 13.6192881254 3 15 3 Z" /><pathandroid:name="toe4"android:fillColor="#ffffff"android:pathData="M 19.5 7 C 20.8807118746 7 22 8.11928812542 22 9.5 C 22 10.8807118746 20.8807118746 12 19.5 12 C 18.1192881254 12 17 10.8807118746 17 9.5 C 17 8.11928812542 18.1192881254 7 19.5 7 Z" /><pathandroid:fillColor="#ffffff"android:pathData="M17.34 14.86c-.87-1.02-1.6-1.89-2.48-2.91-.46-.54-1.05-1.08-1.75-1.32-.11-.04-.22-.07-.33-.09-.25-.04-.52-.04-.78-.04s-.53 0-.79 .05 c-.11 .02 -.22 .05 -.33 .09 -.7 .24 -1.28 .78 -1.75 1.32-.87 1.02-1.6 1.89-2.48 2.91-1.31 1.31-2.92 2.76-2.62 4.79 .29 1.02 1.02 2.03 2.33 2.32 .73 .15 3.06-.44 5.54-.44h.18c2.48 0 4.81 .58 5.54 .44 1.31-.29 2.04-1.31 2.33-2.32 .31 -2.04-1.3-3.49-2.61-4.8z" />
</vector>

2.添加AnimatedVectorDrawable文件

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"android:drawable="@drawable/vd_path_paw"><targetandroid:name="toe1"android:animation="@animator/anim_path_morph_toe1"/><targetandroid:name="toe2"android:animation="@animator/anim_path_morph_toe2"/><targetandroid:name="toe3"android:animation="@animator/anim_path_morph_toe3"/><targetandroid:name="toe4"android:animation="@animator/anim_path_morph_toe4"/>
</animated-vector>

3.根据实现的效果来编写动画文件anim_path_morph_toe1,anim_path_morph_toe2,anim_path_morph_toe3,anim_path_morph_toe4

anim_path_morph_toe1.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"android:ordering="sequentially"><objectAnimatorandroid:duration="420"android:propertyName="pathData"android:repeatCount="-1"android:repeatMode="reverse"android:valueFrom="M 4.5 7 C 5.88071187458 7 7 8.11928812542 7 9.5 C 7 10.8807118746 5.88071187458 12 4.5 12 C 3.11928812542 12 2 10.8807118746 2 9.5 C 2 8.11928812542 3.11928812542 7 4.5 7 Z"android:valueTo="M 4.5 9 C 5.88071187458 9 7 10.1192881254 7 11.5 C 7 12.8807118746 5.88071187458 14 4.5 14 C 3.11928812542 14 2 12.8807118746 2 11.5 C 2 10.1192881254 3.11928812542 9 4.5 9 Z"android:valueType="pathType" /><objectAnimatorandroid:duration="840"android:propertyName="pathData"android:repeatCount="-1"android:repeatMode="reverse"android:valueFrom="M 4.5 9 C 5.88071187458 9 7 10.1192881254 7 11.5 C 7 12.8807118746 5.88071187458 14 4.5 14 C 3.11928812542 14 2 12.8807118746 2 11.5 C 2 10.1192881254 3.11928812542 9 4.5 9 Z"android:valueTo="M 4.5 4 C 5.88071187458 4 7 5.11928812542 7 6.5 C 7 7.88071187458 5.88071187458 9 4.5 9 C 3.11928812542 9 2 7.88071187458 2 6.5 C 2 5.11928812542 3.11928812542 4 4.5 4 Z"android:valueType="pathType" /><objectAnimatorandroid:duration="420"android:propertyName="pathData"android:repeatCount="-1"android:repeatMode="reverse"android:valueFrom="M 4.5 4 C 5.88071187458 4 7 5.11928812542 7 6.5 C 7 7.88071187458 5.88071187458 9 4.5 9 C 3.11928812542 9 2 7.88071187458 2 6.5 C 2 5.11928812542 3.11928812542 4 4.5 4 Z"android:valueTo="M 4.5 7 C 5.88071187458 7 7 8.11928812542 7 9.5 C 7 10.8807118746 5.88071187458 12 4.5 12 C 3.11928812542 12 2 10.8807118746 2 9.5 C 2 8.11928812542 3.11928812542 7 4.5 7 Z"android:valueType="pathType" />
</set>

anim_path_morph_toe2.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"android:ordering="sequentially"android:shareInterpolator="true"><objectAnimatorandroid:duration="480"android:propertyName="pathData"android:repeatCount="-1"android:repeatMode="reverse"android:startOffset="100"android:valueFrom="M 9 3 C 10.3807118746 3 11.5 4.11928812542 11.5 5.5 C 11.5 6.88071187458 10.3807118746 8 9 8 C 7.61928812542 8 6.5 6.88071187458 6.5 5.5 C 6.5 4.11928812542 7.61928812542 3 9 3 Z"android:valueTo="M 9 5 C 10.3807118746 5 11.5 6.11928812542 11.5 7.5 C 11.5 8.88071187458 10.3807118746 10 9 10 C 7.61928812542 10 6.5 8.88071187458 6.5 7.5 C 6.5 6.11928812542 7.61928812542 5 9 5 Z"android:valueType="pathType" /><objectAnimatorandroid:duration="960"android:propertyName="pathData"android:repeatCount="-1"android:repeatMode="reverse"android:valueFrom="M 9 5 C 10.3807118746 5 11.5 6.11928812542 11.5 7.5 C 11.5 8.88071187458 10.3807118746 10 9 10 C 7.61928812542 10 6.5 8.88071187458 6.5 7.5 C 6.5 6.11928812542 7.61928812542 5 9 5 Z"android:valueTo="M 9 0 C 10.3807118746 0 11.5 1.11928812542 11.5 2.5 C 11.5 3.88071187458 10.3807118746 5 9 5 C 7.61928812542 5 6.5 3.88071187458 6.5 2.5 C 6.5 1.11928812542 7.61928812542 0 9 0 Z"android:valueType="pathType" /><objectAnimatorandroid:duration="480"android:propertyName="pathData"android:repeatCount="-1"android:repeatMode="reverse"android:valueFrom="M 9 0 C 10.3807118746 0 11.5 1.11928812542 11.5 2.5 C 11.5 3.88071187458 10.3807118746 5 9 5 C 7.61928812542 5 6.5 3.88071187458 6.5 2.5 C 6.5 1.11928812542 7.61928812542 0 9 0 Z"android:valueTo="M 9 3 C 10.3807118746 3 11.5 4.11928812542 11.5 5.5 C 11.5 6.88071187458 10.3807118746 8 9 8 C 7.61928812542 8 6.5 6.88071187458 6.5 5.5 C 6.5 4.11928812542 7.61928812542 3 9 3 Z"android:valueType="pathType" />
</set>

anim_path_morph_toe3.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"android:ordering="sequentially"android:shareInterpolator="true"><objectAnimatorandroid:duration="420"android:propertyName="pathData"android:repeatCount="-1"android:repeatMode="reverse"android:startOffset="200"android:valueFrom="M 15 3 C 16.3807118746 3 17.5 4.11928812542 17.5 5.5 C 17.5 6.88071187458 16.3807118746 8 15 8 C 13.6192881254 8 12.5 6.88071187458 12.5 5.5 C 12.5 4.11928812542 13.6192881254 3 15 3 Z"android:valueTo="M 15 5 C 16.3807118746 5 17.5 6.11928812542 17.5 7.5 C 17.5 8.88071187458 16.3807118746 10 15 10 C 13.6192881254 10 12.5 8.88071187458 12.5 7.5 C 12.5 6.11928812542 13.6192881254 5 15 5 Z"android:valueType="pathType" /><objectAnimatorandroid:duration="840"android:propertyName="pathData"android:repeatCount="-1"android:repeatMode="reverse"android:valueFrom="M 15 5 C 16.3807118746 5 17.5 6.11928812542 17.5 7.5 C 17.5 8.88071187458 16.3807118746 10 15 10 C 13.6192881254 10 12.5 8.88071187458 12.5 7.5 C 12.5 6.11928812542 13.6192881254 5 15 5 Z"android:valueTo="M 15 0 C 16.3807118746 0 17.5 1.11928812542 17.5 2.5 C 17.5 3.88071187458 16.3807118746 5 15 5 C 13.6192881254 5 12.5 3.88071187458 12.5 2.5 C 12.5 1.11928812542 13.6192881254 0 15 0 Z"android:valueType="pathType" /><objectAnimatorandroid:duration="420"android:propertyName="pathData"android:repeatCount="-1"android:repeatMode="reverse"android:valueFrom="M 15 0 C 16.3807118746 0 17.5 1.11928812542 17.5 2.5 C 17.5 3.88071187458 16.3807118746 5 15 5 C 13.6192881254 5 12.5 3.88071187458 12.5 2.5 C 12.5 1.11928812542 13.6192881254 0 15 0 Z"android:valueTo="M 15 3 C 16.3807118746 3 17.5 4.11928812542 17.5 5.5 C 17.5 6.88071187458 16.3807118746 8 15 8 C 13.6192881254 8 12.5 6.88071187458 12.5 5.5 C 12.5 4.11928812542 13.6192881254 3 15 3 Z"android:valueType="pathType" />
</set>

anim_path_morph_toe4.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"android:ordering="sequentially"android:shareInterpolator="true"><objectAnimatorandroid:duration="450"android:propertyName="pathData"android:repeatCount="-1"android:repeatMode="reverse"android:startOffset="300"android:valueFrom="M 19.5 7 C 20.8807118746 7 22 8.11928812542 22 9.5 C 22 10.8807118746 20.8807118746 12 19.5 12 C 18.1192881254 12 17 10.8807118746 17 9.5 C 17 8.11928812542 18.1192881254 7 19.5 7 Z"android:valueTo="M 19.5 9 C 20.8807118746 9 22 10.1192881254 22 11.5 C 22 12.8807118746 20.8807118746 14 19.5 14 C 18.1192881254 14 17 12.8807118746 17 11.5 C 17 10.1192881254 18.1192881254 9 19.5 9 Z"android:valueType="pathType" /><objectAnimatorandroid:duration="900"android:propertyName="pathData"android:repeatCount="-1"android:repeatMode="reverse"android:valueFrom="M 19.5 7 C 20.8807118746 7 22 8.11928812542 22 9.5 C 22 10.8807118746 20.8807118746 12 19.5 12 C 18.1192881254 12 17 10.8807118746 17 9.5 C 17 8.11928812542 18.1192881254 7 19.5 7 Z"android:valueTo="M 19.5 4 C 20.8807118746 4 22 5.11928812542 22 6.5 C 22 7.88071187458 20.8807118746 9 19.5 9 C 18.1192881254 9 17 7.88071187458 17 6.5 C 17 5.11928812542 18.1192881254 4 19.5 4 Z"android:valueType="pathType" /><objectAnimatorandroid:duration="450"android:propertyName="pathData"android:repeatCount="-1"android:repeatMode="reverse"android:valueFrom="M 19.5 4 C 20.8807118746 4 22 5.11928812542 22 6.5 C 22 7.88071187458 20.8807118746 9 19.5 9 C 18.1192881254 9 17 7.88071187458 17 6.5 C 17 5.11928812542 18.1192881254 4 19.5 4 Z"android:valueTo="M 19.5 7 C 20.8807118746 7 22 8.11928812542 22 9.5 C 22 10.8807118746 20.8807118746 12 19.5 12 C 18.1192881254 12 17 10.8807118746 17 9.5 C 17 8.11928812542 18.1192881254 7 19.5 7 Z"android:valueType="pathType" />
</set>

(九)实现一个具有交互效果的中国地图

实现步骤:
1.首先从网站上去下载中国地图的SVG,然后再通过工具将该svg文件转换为Android中的Vector Drawable资源
2.将转换后的chinahigh.xml文件放入项目的drawable目录下
3.接下来解析chinahigh.xml文件将从xml中解析的Path相关数据保存到ProvinceItem对象中
4.然后自定义View-ChinaMap在onDraw方法中绘制解析出来的所有Path
5.通过监听View的onTouchEvent方法来实现点击地图填充显示效果

使用到的关键技术
1.XmlPullParser解析xml文件,获取Path中的数据
2.将android:pathData中的字符串转换成Path类,使用工具类PathParser
3.判断当前用户点击位置在哪个省的区域中,这里先将Path的外围矩形区域计算出来,然后通过给Region设置一个Path和矩形区域来进行取交集,得到省份所在区域,再通过Region的contains(x,y)方法来判断当前点击的省份

自定义地图View ChinaMap代码如下:

public class ChinaMap extends View {//画笔颜色值数组private static final int color[]={Color.RED,Color.BLACK,Color.GREEN,Color.CYAN};private List<ProvinceItem> provinceList;//存储所有省的Path数据private Context mContext;private Paint mPaint;private float scale=1.3f;//为了地图能完整显示到手机上,这里对其进行一定缩放private ExecutorService mExecutorService;public ChinaMap(Context context) {this(context, null);}public ChinaMap(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public ChinaMap(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);this.mContext=context;initData();}private void initData(){mPaint=new Paint(Paint.ANTI_ALIAS_FLAG);mPaint.setStyle(Paint.Style.STROKE);mPaint.setColor(Color.BLUE);mPaint.setStrokeWidth(5);provinceList=new ArrayList<>();//使用线程池开启一个线程来对Vector Drawable 文件中的数据进行解析mExecutorService= Executors.newSingleThreadExecutor();mExecutorService.execute(new Runnable() {@Overridepublic void run() {parseXml(mContext);}});}/*解析地图文件VectorDrawable数据 将解析的数据封装到provinceList集合中*/private void parseXml(Context context) {InputStream inputStream = null;try {//获取Vector文件资源的流inputStream = context.getResources().openRawResource(R.raw.chinahigh);//创建XmlPullParser用于解析xml文件XmlPullParser xmlParser = Xml.newPullParser();//设置要解析的文件流和流读取格式xmlParser.setInput(inputStream, "utf-8");//获取当前标签的类型int eventType=xmlParser.getEventType();//当获取的标签是文档结束标签时,不再读取文件while (eventType != XmlPullParser.END_DOCUMENT){//判断当前标签为开始标签 例如:开始标签<path> 结束标签</path>if(eventType == XmlPullParser.START_TAG){//如果当前标签为path则会获取它的相关数据if("path".equals(xmlParser.getName())){//创建一个实体类用于保存Path的信息ProvinceItem provinceEntity=new ProvinceItem();//这里是获取path标签的android:pathData这个属性的值 属性索引为3String text=xmlParser.getAttributeValue(3);//将字符串数据解析成Path对象Path path=PathParser.createPathFromPathData(text);provinceEntity.setPath(path);//这里为每一个省 随机一个颜色值显示provinceEntity.setFillColor(color[(int) (Math.random()*(color.length-1))]);provinceList.add(provinceEntity);}}eventType=xmlParser.next();//获取下一个标签}//xml文件解析完成后,通知界面刷新UIpostInvalidate();} catch (Exception e) {e.printStackTrace();} finally {if (inputStream != null) {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}}}@Overrideprotected void onDraw(Canvas canvas) {//绘制解析出来的所有省份if(provinceList != null && provinceList.size()>0){//将画布缩放 以便地图正常显示到手机上canvas.scale(scale, scale);for (ProvinceItem province:provinceList){//通知每个省进行界面绘制province.onDraw(canvas,mPaint);}}}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()){//监听按下事件 当用户按下地图时选中按下所在区域地图case MotionEvent.ACTION_DOWN:if(provinceList != null && provinceList.size()>0){//判断当前手指点击是否在某个省的范围内for (ProvinceItem province:provinceList){//因为上面对canvas进行了缩放 这里需要对点击的坐标进行相应缩放,才能取到正确区域位置province.checkTouchScope(event.getX()/scale,event.getY()/scale);}//刷新地图显示postInvalidate();}break;}return super.onTouchEvent(event);}
}

每个省份代表的区域用ProvinceItem类封装实现,代码如下:

public class ProvinceItem {private Path mPath;//该省在地图上的路径 需要将path绘制到View上private int mFillColor;//绘制该省的颜色private boolean isSelected;//表示是否点击了此区域public Path getPath() {return mPath;}public void setPath(Path mPath) {this.mPath = mPath;}public int getFillColor() {return mFillColor;}public void setFillColor(int mFillColor) {this.mFillColor = mFillColor;}/*** 每个省自己绘制自己的Path* @param canvas* @param paint*/public void onDraw(Canvas canvas, Paint paint){//被选中的区域则使用填充方式绘制地图if(isSelected){paint.setStyle(Paint.Style.FILL_AND_STROKE);}else {paint.setStyle(Paint.Style.STROKE);}paint.setColor(mFillColor);canvas.drawPath(mPath,paint);}/*** 检查当前手指点击位置是否为本省所在区域* @param x x位置* @param y y位置*/public void checkTouchScope(float x,float y){//创建一个区域 用于表示当前省份所在区域Region region=new Region();//创建一个矩形 此为path路径所在区域的最小矩形RectF pathBound=new RectF();//计算Path外围的最小矩形 将计算的值保存到pathBound中mPath.computeBounds(pathBound,false);//该方法用于计算Path所在区域与它的外围最小矩形区域之间的交集区域 将交集区域保存到region中region.setPath(mPath,new Region((int) pathBound.left,(int)pathBound.top,(int)pathBound.right,(int)pathBound.bottom));//判断当前手值按下的位置是否在当前省份的区域当中isSelected=region.contains((int)x,(int) y);}}

使用到的工具类,将path标签中的pathData字符串数据转换成Path类

/*** path 路径解析兼容类(兼容标准svg)*/
public class PathParser {private static final String LOG_TAG = "PathParser";// Copy from Arrays.copyOfRange() which is only available from API level 9./*** Copies elements from {@code original} into a new array, from indexes start (inclusive) to* end (exclusive). The original order of elements is preserved.* If {@code end} is greater than {@code original.length}, the result is padded* with the value {@code 0.0f}.** @param original the original array* @param start    the start index, inclusive* @param end      the end index, exclusive* @return the new array* @throws IllegalArgumentException       if {@code start > end}* @throws NullPointerException           if {@code original == null}*/private static float[] copyOfRange(float[] original, int start, int end) {if (start > end) {throw new IllegalArgumentException();}int originalLength = original.length;if (start < 0 || start > originalLength) {throw new ArrayIndexOutOfBoundsException();}int resultLength = end - start;int copyLength = Math.min(resultLength, originalLength - start);float[] result = new float[resultLength];System.arraycopy(original, start, result, 0, copyLength);return result;}/*** @param pathData The string representing a path, the same as "d" string in svg file.* @return the generated Path object.*/public static Path createPathFromPathData(String pathData) {Path path = new Path();PathDataNode[] nodes = createNodesFromPathData(pathData);if (nodes != null) {try {PathDataNode.nodesToPath(nodes, path);} catch (RuntimeException e) {throw new RuntimeException("Error in parsing " + pathData, e);}return path;}return null;}/*** @param pathData The string representing a path, the same as "d" string in svg file.* @return an array of the PathDataNode.*/public static PathDataNode[] createNodesFromPathData(String pathData) {if (pathData == null) {return null;}int start = 0;int end = 1;ArrayList<PathDataNode> list = new ArrayList<PathDataNode>();while (end < pathData.length()) {end = nextStart(pathData, end);String s = pathData.substring(start, end).trim();if (s.length() > 0) {float[] val = getFloats(s);addNode(list, s.charAt(0), val);}start = end;end++;}if ((end - start) == 1 && start < pathData.length()) {addNode(list, pathData.charAt(start), new float[0]);}return list.toArray(new PathDataNode[list.size()]);}/*** @param source The array of PathDataNode to be duplicated.* @return a deep copy of the <code>source</code>.*/public static PathDataNode[] deepCopyNodes(PathDataNode[] source) {if (source == null) {return null;}PathDataNode[] copy = new PathDataNode[source.length];for (int i = 0; i < source.length; i++) {copy[i] = new PathDataNode(source[i]);}return copy;}/*** @param nodesFrom The source path represented in an array of PathDataNode* @param nodesTo   The target path represented in an array of PathDataNode* @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code>*/public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) {if (nodesFrom == null || nodesTo == null) {return false;}if (nodesFrom.length != nodesTo.length) {return false;}for (int i = 0; i < nodesFrom.length; i++) {if (nodesFrom[i].type != nodesTo[i].type|| nodesFrom[i].params.length != nodesTo[i].params.length) {return false;}}return true;}/*** Update the target's data to match the source.* Before calling this, make sure canMorph(target, source) is true.** @param target The target path represented in an array of PathDataNode* @param source The source path represented in an array of PathDataNode*/public static void updateNodes(PathDataNode[] target, PathDataNode[] source) {for (int i = 0; i < source.length; i++) {target[i].type = source[i].type;for (int j = 0; j < source[i].params.length; j++) {target[i].params[j] = source[i].params[j];}}}private static int nextStart(String s, int end) {char c;while (end < s.length()) {c = s.charAt(end);// Note that 'e' or 'E' are not valid path commands, but could be// used for floating point numbers' scientific notation.// Therefore, when searching for next command, we should ignore 'e'// and 'E'.if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))&& c != 'e' && c != 'E') {return end;}end++;}return end;}private static void addNode(ArrayList<PathDataNode> list, char cmd, float[] val) {list.add(new PathDataNode(cmd, val));}private static class ExtractFloatResult {// We need to return the position of the next separator and whether the// next float starts with a '-' or a '.'.int mEndPosition;boolean mEndWithNegOrDot;}/*** Parse the floats in the string.* This is an optimized version of parseFloat(s.split(",|\\s"));** @param s the string containing a command and list of floats* @return array of floats*/private static float[] getFloats(String s) {if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') {return new float[0];}try {float[] results = new float[s.length()];int count = 0;int startPosition = 1;int endPosition = 0;ExtractFloatResult result = new ExtractFloatResult();int totalLength = s.length();// The startPosition should always be the first character of the// current number, and endPosition is the character after the current// number.while (startPosition < totalLength) {extract(s, startPosition, result);endPosition = result.mEndPosition;if (startPosition < endPosition) {results[count++] = Float.parseFloat(s.substring(startPosition, endPosition));}if (result.mEndWithNegOrDot) {// Keep the '-' or '.' sign with next number.startPosition = endPosition;} else {startPosition = endPosition + 1;}}return copyOfRange(results, 0, count);} catch (NumberFormatException e) {throw new RuntimeException("error in parsing \"" + s + "\"", e);}}/*** Calculate the position of the next comma or space or negative sign** @param s      the string to search* @param start  the position to start searching* @param result the result of the extraction, including the position of the*               the starting position of next number, whether it is ending with a '-'.*/private static void extract(String s, int start, ExtractFloatResult result) {// Now looking for ' ', ',', '.' or '-' from the start.int currentIndex = start;boolean foundSeparator = false;result.mEndWithNegOrDot = false;boolean secondDot = false;boolean isExponential = false;for (; currentIndex < s.length(); currentIndex++) {boolean isPrevExponential = isExponential;isExponential = false;char currentChar = s.charAt(currentIndex);switch (currentChar) {case ' ':case ',':foundSeparator = true;break;case '-':// The negative sign following a 'e' or 'E' is not a separator.if (currentIndex != start && !isPrevExponential) {foundSeparator = true;result.mEndWithNegOrDot = true;}break;case '.':if (!secondDot) {secondDot = true;} else {// This is the second dot, and it is considered as a separator.foundSeparator = true;result.mEndWithNegOrDot = true;}break;case 'e':case 'E':isExponential = true;break;}if (foundSeparator) {break;}}// When there is nothing found, then we put the end position to the end// of the string.result.mEndPosition = currentIndex;}/*** Each PathDataNode represents one command in the "d" attribute of the svg* file.* An array of PathDataNode can represent the whole "d" attribute.*/public static class PathDataNode {/*package*/char type;float[] params;private PathDataNode(char type, float[] params) {this.type = type;this.params = params;}private PathDataNode(PathDataNode n) {type = n.type;params = copyOfRange(n.params, 0, n.params.length);}/*** Convert an array of PathDataNode to Path.** @param node The source array of PathDataNode.* @param path The target Path object.*/public static void nodesToPath(PathDataNode[] node, Path path) {float[] current = new float[6];char previousCommand = 'm';for (int i = 0; i < node.length; i++) {addCommand(path, current, previousCommand, node[i].type, node[i].params);previousCommand = node[i].type;}}/*** The current PathDataNode will be interpolated between the* <code>nodeFrom</code> and <code>nodeTo</code> according to the* <code>fraction</code>.** @param nodeFrom The start value as a PathDataNode.* @param nodeTo   The end value as a PathDataNode* @param fraction The fraction to interpolate.*/public void interpolatePathDataNode(PathDataNode nodeFrom,PathDataNode nodeTo, float fraction) {for (int i = 0; i < nodeFrom.params.length; i++) {params[i] = nodeFrom.params[i] * (1 - fraction)+ nodeTo.params[i] * fraction;}}private static void addCommand(Path path, float[] current,char previousCmd, char cmd, float[] val) {int incr = 2;float currentX = current[0];float currentY = current[1];float ctrlPointX = current[2];float ctrlPointY = current[3];float currentSegmentStartX = current[4];float currentSegmentStartY = current[5];float reflectiveCtrlPointX;float reflectiveCtrlPointY;switch (cmd) {case 'z':case 'Z':path.close();// Path is closed here, but we need to move the pen to the// closed position. So we cache the segment's starting position,// and restore it here.currentX = currentSegmentStartX;currentY = currentSegmentStartY;ctrlPointX = currentSegmentStartX;ctrlPointY = currentSegmentStartY;path.moveTo(currentX, currentY);break;case 'm':case 'M':case 'l':case 'L':case 't':case 'T':incr = 2;break;case 'h':case 'H':case 'v':case 'V':incr = 1;break;case 'c':case 'C':incr = 6;break;case 's':case 'S':case 'q':case 'Q':incr = 4;break;case 'a':case 'A':incr = 7;break;}for (int k = 0; k < val.length; k += incr) {switch (cmd) {case 'm': // moveto - Start a new sub-path (relative)currentX += val[k + 0];currentY += val[k + 1];if (k > 0) {// According to the spec, if a moveto is followed by multiple// pairs of coordinates, the subsequent pairs are treated as// implicit lineto commands.path.rLineTo(val[k + 0], val[k + 1]);} else {path.rMoveTo(val[k + 0], val[k + 1]);currentSegmentStartX = currentX;currentSegmentStartY = currentY;}break;case 'M': // moveto - Start a new sub-pathcurrentX = val[k + 0];currentY = val[k + 1];if (k > 0) {// According to the spec, if a moveto is followed by multiple// pairs of coordinates, the subsequent pairs are treated as// implicit lineto commands.path.lineTo(val[k + 0], val[k + 1]);} else {path.moveTo(val[k + 0], val[k + 1]);currentSegmentStartX = currentX;currentSegmentStartY = currentY;}break;case 'l': // lineto - Draw a line from the current point (relative)path.rLineTo(val[k + 0], val[k + 1]);currentX += val[k + 0];currentY += val[k + 1];break;case 'L': // lineto - Draw a line from the current pointpath.lineTo(val[k + 0], val[k + 1]);currentX = val[k + 0];currentY = val[k + 1];break;case 'h': // horizontal lineto - Draws a horizontal line (relative)path.rLineTo(val[k + 0], 0);currentX += val[k + 0];break;case 'H': // horizontal lineto - Draws a horizontal linepath.lineTo(val[k + 0], currentY);currentX = val[k + 0];break;case 'v': // vertical lineto - Draws a vertical line from the current point (r)path.rLineTo(0, val[k + 0]);currentY += val[k + 0];break;case 'V': // vertical lineto - Draws a vertical line from the current pointpath.lineTo(currentX, val[k + 0]);currentY = val[k + 0];break;case 'c': // curveto - Draws a cubic Bézier curve (relative)path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],val[k + 4], val[k + 5]);ctrlPointX = currentX + val[k + 2];ctrlPointY = currentY + val[k + 3];currentX += val[k + 4];currentY += val[k + 5];break;case 'C': // curveto - Draws a cubic Bézier curvepath.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],val[k + 4], val[k + 5]);currentX = val[k + 4];currentY = val[k + 5];ctrlPointX = val[k + 2];ctrlPointY = val[k + 3];break;case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)reflectiveCtrlPointX = 0;reflectiveCtrlPointY = 0;if (previousCmd == 'c' || previousCmd == 's'|| previousCmd == 'C' || previousCmd == 'S') {reflectiveCtrlPointX = currentX - ctrlPointX;reflectiveCtrlPointY = currentY - ctrlPointY;}path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,val[k + 0], val[k + 1],val[k + 2], val[k + 3]);ctrlPointX = currentX + val[k + 0];ctrlPointY = currentY + val[k + 1];currentX += val[k + 2];currentY += val[k + 3];break;case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)reflectiveCtrlPointX = currentX;reflectiveCtrlPointY = currentY;if (previousCmd == 'c' || previousCmd == 's'|| previousCmd == 'C' || previousCmd == 'S') {reflectiveCtrlPointX = 2 * currentX - ctrlPointX;reflectiveCtrlPointY = 2 * currentY - ctrlPointY;}path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,val[k + 0], val[k + 1], val[k + 2], val[k + 3]);ctrlPointX = val[k + 0];ctrlPointY = val[k + 1];currentX = val[k + 2];currentY = val[k + 3];break;case 'q': // Draws a quadratic Bézier (relative)path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);ctrlPointX = currentX + val[k + 0];ctrlPointY = currentY + val[k + 1];currentX += val[k + 2];currentY += val[k + 3];break;case 'Q': // Draws a quadratic Bézierpath.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);ctrlPointX = val[k + 0];ctrlPointY = val[k + 1];currentX = val[k + 2];currentY = val[k + 3];break;case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)reflectiveCtrlPointX = 0;reflectiveCtrlPointY = 0;if (previousCmd == 'q' || previousCmd == 't'|| previousCmd == 'Q' || previousCmd == 'T') {reflectiveCtrlPointX = currentX - ctrlPointX;reflectiveCtrlPointY = currentY - ctrlPointY;}path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,val[k + 0], val[k + 1]);ctrlPointX = currentX + reflectiveCtrlPointX;ctrlPointY = currentY + reflectiveCtrlPointY;currentX += val[k + 0];currentY += val[k + 1];break;case 'T': // Draws a quadratic Bézier curve (reflective control point)reflectiveCtrlPointX = currentX;reflectiveCtrlPointY = currentY;if (previousCmd == 'q' || previousCmd == 't'|| previousCmd == 'Q' || previousCmd == 'T') {reflectiveCtrlPointX = 2 * currentX - ctrlPointX;reflectiveCtrlPointY = 2 * currentY - ctrlPointY;}path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,val[k + 0], val[k + 1]);ctrlPointX = reflectiveCtrlPointX;ctrlPointY = reflectiveCtrlPointY;currentX = val[k + 0];currentY = val[k + 1];break;case 'a': // Draws an elliptical arc// (rx ry x-axis-rotation large-arc-flag sweep-flag x y)drawArc(path,currentX,currentY,val[k + 5] + currentX,val[k + 6] + currentY,val[k + 0],val[k + 1],val[k + 2],val[k + 3] != 0,val[k + 4] != 0);currentX += val[k + 5];currentY += val[k + 6];ctrlPointX = currentX;ctrlPointY = currentY;break;case 'A': // Draws an elliptical arcdrawArc(path,currentX,currentY,val[k + 5],val[k + 6],val[k + 0],val[k + 1],val[k + 2],val[k + 3] != 0,val[k + 4] != 0);currentX = val[k + 5];currentY = val[k + 6];ctrlPointX = currentX;ctrlPointY = currentY;break;}previousCmd = cmd;}current[0] = currentX;current[1] = currentY;current[2] = ctrlPointX;current[3] = ctrlPointY;current[4] = currentSegmentStartX;current[5] = currentSegmentStartY;}private static void drawArc(Path p,float x0,float y0,float x1,float y1,float a,float b,float theta,boolean isMoreThanHalf,boolean isPositiveArc) {/* Convert rotation angle from degrees to radians */double thetaD = Math.toRadians(theta);/* Pre-compute rotation matrix entries */double cosTheta = Math.cos(thetaD);double sinTheta = Math.sin(thetaD);/* Transform (x0, y0) and (x1, y1) into unit space *//* using (inverse) rotation, followed by (inverse) scale */double x0p = (x0 * cosTheta + y0 * sinTheta) / a;double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;double x1p = (x1 * cosTheta + y1 * sinTheta) / a;double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;/* Compute differences and averages */double dx = x0p - x1p;double dy = y0p - y1p;double xm = (x0p + x1p) / 2;double ym = (y0p + y1p) / 2;/* Solve for intersecting unit circles */double dsq = dx * dx + dy * dy;if (dsq == 0.0) {Log.w(LOG_TAG, " Points are coincident");return; /* Points are coincident */}double disc = 1.0 / dsq - 1.0 / 4.0;if (disc < 0.0) {Log.w(LOG_TAG, "Points are too far apart " + dsq);float adjust = (float) (Math.sqrt(dsq) / 1.99999);drawArc(p, x0, y0, x1, y1, a * adjust,b * adjust, theta, isMoreThanHalf, isPositiveArc);return; /* Points are too far apart */}double s = Math.sqrt(disc);double sdx = s * dx;double sdy = s * dy;double cx;double cy;if (isMoreThanHalf == isPositiveArc) {cx = xm - sdy;cy = ym + sdx;} else {cx = xm + sdy;cy = ym - sdx;}double eta0 = Math.atan2((y0p - cy), (x0p - cx));double eta1 = Math.atan2((y1p - cy), (x1p - cx));double sweep = (eta1 - eta0);if (isPositiveArc != (sweep >= 0)) {if (sweep > 0) {sweep -= 2 * Math.PI;} else {sweep += 2 * Math.PI;}}cx *= a;cy *= b;double tcx = cx;cx = cx * cosTheta - cy * sinTheta;cy = tcx * sinTheta + cy * cosTheta;arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);}/*** Converts an arc to cubic Bezier segments and records them in p.** @param p     The target for the cubic Bezier segments* @param cx    The x coordinate center of the ellipse* @param cy    The y coordinate center of the ellipse* @param a     The radius of the ellipse in the horizontal direction* @param b     The radius of the ellipse in the vertical direction* @param e1x   E(eta1) x coordinate of the starting point of the arc* @param e1y   E(eta2) y coordinate of the starting point of the arc* @param theta The angle that the ellipse bounding rectangle makes with horizontal plane* @param start The start angle of the arc on the ellipse* @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse*/private static void arcToBezier(Path p,double cx,double cy,double a,double b,double e1x,double e1y,double theta,double start,double sweep) {// Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html// and http://www.spaceroots.org/documents/ellipse/node22.html// Maximum of 45 degrees per cubic Bezier segmentint numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));double eta1 = start;double cosTheta = Math.cos(theta);double sinTheta = Math.sin(theta);double cosEta1 = Math.cos(eta1);double sinEta1 = Math.sin(eta1);double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);double anglePerSegment = sweep / numSegments;for (int i = 0; i < numSegments; i++) {double eta2 = eta1 + anglePerSegment;double sinEta2 = Math.sin(eta2);double cosEta2 = Math.cos(eta2);double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;double tanDiff2 = Math.tan((eta2 - eta1) / 2);double alpha =Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;double q1x = e1x + alpha * ep1x;double q1y = e1y + alpha * ep1y;double q2x = e2x - alpha * ep2x;double q2y = e2y - alpha * ep2y;p.cubicTo((float) q1x,(float) q1y,(float) q2x,(float) q2y,(float) e2x,(float) e2y);eta1 = eta2;e1x = e2x;e1y = e2y;ep1x = ep2x;ep1y = ep2y;}}}
}

SVG下载地址1
SVG下载地址2
SVG下载地址3
图片转成SVG
SVG图片编辑
SVG转VectorDrawable
SVG相关教程
相关类型图片对比
地图相关SVG地址

SVG相关知识与应用相关推荐

  1. Varnish的相关知识,varnish的简单应用

    Varnish是一款高性能的缓存加速器,具有稳定,且效率更高,资源占用更少等特点. 缓存存储的格式: key-value: key:访问路径,URL, hash value:web content 命 ...

  2. JS作用域相关知识(#精)

    在学习<你不知道的JS>一书中,特将作用域相关知识在此分享一下: #说到作用域,就不得不提到LHS查询和RHS查询: 1)如果查询目的是对变量进行赋值,则使用LHS查询 2)如果查询目的是 ...

  3. 工业相机参数之帧率相关知识详解

    点击上方"小白学视觉",选择加"星标"或"置顶"重磅干货,第一时间送达 工业相机是机器视觉系统的重要组成部分之一,在机器视觉系统中有着非常重 ...

  4. shell的相关知识(变量、脚本定义)

    一.shell的相关知识: 1.对于shell编程语言大体分为:机器语言.汇编语言.高级语言 2.shell变量类型:事先确定数据的存储格式和长度 shell变量分为:字符型.数值型 数值型又分为:整 ...

  5. 视频压缩算法的相关知识

    视频压缩算法的相关知识 MPEG-1 MPEG 视频压缩编码后包括三种元素:I帧(I-frames).P帧(P-frames)和B帧(B-frames).在MPEG编码的过程中,部分视频帧序列压缩成为 ...

  6. linux 格式化 dvd,linux 服务器分区格式化相关知识 -mount

    关于linux 系统mount和mkfs 的相关知识: 使用mount 1)Mount的相关格式:mount [-t 文件类型][-o  选项] devicedir 详解: -t 文件类型,通常默认m ...

  7. WinForm开发,窗体显示和窗体传值相关知识总结

    以前对WinForm窗体显示和窗体间传值了解不是很清楚 最近做了一些WinForm开发,把用到的相关知识整理如下 A.WinForm中窗体显示显示窗体可以有以下2种方法: Form.ShowDialo ...

  8. js基础--数据类型检测的相关知识

    欢迎访问我的个人博客:www.xiaolongwu.cn 前言 最近工作有点忙,好几天都没更新技术博客了. 周末起床打开有道云笔记,发现自己的博客todolist里躺了一堆只有名字的文件. 话不多说, ...

  9. 转载:关于错排的相关知识

    转载:关于错排的相关知识 杭电2048相关知识充电 转自:错排公式 分类: 数论 关于程序2012-06-08 19:07 335人阅读 评论(0) 收藏 举报 n2 错排问题 错排问题 就是一种递推 ...

最新文章

  1. 30 个 php 操作 redis 常用方法代码例子
  2. mysql case when 去重_【Mysql】 case ... when ... 用法
  3. 查询Linux系统最后重启时间的三个方法
  4. COGS——T 8. 备用交换机
  5. cmd后台运行exe_windows 十大实用“运行”命令
  6. Django-Model中的ForeignKey,ManyToManyField与OneToOneField
  7. PYTHON自动化Day4-交换变量,字符串方法,拷贝,集合,文件,文件指针
  8. python实例 87,88
  9. 电子助力方向机控制模块_17款路虎揽胜:偶发性电子助力失效
  10. python中数字的排序
  11. win10系统字体 chrome 修改苹果字体
  12. 期权波动率套利策略之谜
  13. 硬盘出现“文件或目录损坏且无法读取”的故障,怎么解决?
  14. 守望空城,一位摄影师镜头下的武汉
  15. 计算机考研每日安排,计算机考研如何安排复习计划
  16. python运维脚本简书_通过python+selenium3实现浏览器刷简书文章阅读量
  17. 信道容量、数字基带与带通传输系统误码率公式整理
  18. 还在为创业资金发愁?1000万元+产业资源等你来拿!
  19. Bellman-Ford Spfa
  20. 照做的话,发不了SCI论文你来找我

热门文章

  1. python使用多线程进行爬豆瓣电影top250海报图片,附源码加运行结果
  2. 错题本——数据库系统工程师 2009
  3. kali搭建bind9 dns服务器启动问题:Failed to restart bind9.service: Unit bind9.service not found.
  4. Riak的一个PHP扩展
  5. Riak学习(2):java连接Riak服务,使用Protocol Buffers连接
  6. UAP 添加字典表
  7. 基于百度地图的python开发服务端_百度地图api用python行吗
  8. 栈,队列(纸牌游戏,小猫钓鱼)
  9. 如何清理ubunu的空间--使用磁盘使用情况分析器
  10. 多项式求值的秦九韶算法