之前对矢量图形有所耳闻,但因为Android对于矢量图形的原生支持较晚,所以一直没好好研究过(16年2月25就出来的东西,其实就是懒 =。=)。最近工作上正好遇到一个产品需求,想用SVG来解决,借此机会对SVG及Android对于矢量图形的支持做了次了解,当然最后依然被SVG坑到,变成手写XML来解决需求,不过这都是题外话了。
SVG是什么?
Scalable Vector Graphics(可缩放矢量图形)是基于XML,用于描述二维矢量图形的图形格式。SVG由W3C制定,是一个开放标准。SVG本身允许包含3种图形对象类型:
- 矢量图形,包括矩形、圆、椭圆、多边形、直线、任意曲线等。
- 嵌入外部图像,包括png、jpeg、svg等。
- 文本。
[from wikipedia]
以下是一个简单的SVG格式示例:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" />
<path d="M150 0 L75 200 L225 200 Z" />
</svg>
SVG是通过标签的形式来描述图形,比如<rect>
矩形、<circle>
圆形、<polygon>
多边形等等普通标签,以及支持复杂的路径的标签<path>
.其中Path标签可以通俗的理解为是用命令的方式来控制画笔,比如:
** 将画笔移动到指定坐标位置 -> 画一条直线到指定坐标位置 -> 再画一条曲线 -> 完成后抬起画笔结束**
当然Path标签也需要对应的指令来完成操作,比如:
M150 0 L75 200 L225 200 Z
下面是Path支持的对应指令:
M = moveto(M X,Y) :将画笔移动到指定的坐标位置
L = lineto(L X,Y) :画直线到指定的坐标位置
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):弧线
Z = closepath():关闭路径
注意:以上所有命令均允许小写字母。大写表示绝对定位,小写表示相对定位。
当然,以上只是SVG中很小的一部分,还有其他很多强大的功能。SVG本身已经被广泛使用,SVG矢量图与位图的差别,在于矢量图可以尽可能的放大而不失真,并且同一张图像,SVG的实现可能比png小很多。如果在精简安装包的时候,可以考虑部分图标改成矢量图的方式。
Android中的矢量图替身Vector
Android中对于矢量图形的处理,并没有选择直接支持SVG文件,所以云端返回的svg格式图片是无法直接被显示的。
不过在Android 5.0中提出了矢量图片的支持,叫做Vector。通过创建<vector>标签的xml元素来完成对矢量图形的定义。
<!-- res/drawable/heart.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
<!-- intrinsic size of the drawable -->
android:height="256dp"
android:width="256dp"
<!-- size of the virtual canvas -->
android:viewportWidth="32"
android:viewportHeight="32">
<!-- draw a path -->
<path android:fillColor="#8fff"
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>
Android Studio从1.4版本开始,包含了一个Vector Asset Studio的工具,可以选择导入SVG或者PSD文件来帮助我们生成对应的vector xml文件。
工具本身支持大多数主体元素,但并不包含所有的SVG或PSD特性。SVG导入后的vector xml格式主体参考的是SVG中的pathdata语法,但虚线等属性、科学计数法、图案填充(pattern)、渐变(gradient)等特定标签是不支持的。当然Vector Asset Studio在我们导入的时候会有明确的提示哪些是被支持的、哪些是不被支持的。
以下两种方法都可以打开Vector Asset Studio:
1. 对应的drawable目录上右键 -> New -> Vector Asset
2. File -> New -> Vector Asset
Android项目中如何使用Vector?
从Android 5.0开始,系统提供了两个Api来完成对Vector的支持:
- VectorDrawable
- AnimatedVectorDrawable
VectorDrawable负责矢量图形的显示,而AnimatedVectorDrawable负责基于矢量图形的动画效果。
与此同时,Android Support Library 23.2.0及之后的版本中,也加入了对Vector的向下兼容,引入了VectorDrawableCompat和AnimatedVectorDrawableCompat。
只需要引入对应的AppCompat库
compile 'com.android.support:appcompat-v7:23.2.0'
VectorDrawable API 7+
AnimatedVectorDrawable API 11+
另外需要在 build.gradle 文件中新增如下配置:
//For Gradle Plugin 2.0+
android {
defaultConfig {
vectorDrawables.useSupportLibrary = true
}
}
//For Gradle Plugin 1.5 or below
android {
defaultConfig {
// Stops the Gradle plugin’s automatic rasterization of vectors
generatedDensities = []
}
// Flag notifies aapt to keep the attribute IDs around
aaptOptions {
additionalParameters "--no-version-vectors"
}
}
如何使用VectorDrawable?
在布局文件中,我们可以针对ImageVIew、ImageButton等控制设置对应的矢量图片。
如果用的Support包,那只需要通过app:srcCompat=“”
属性来替代以前的android:src
即可。
另外记得添加xmlns:app="http://schemas.android.com/apk/res-auto"
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_build_black_24dp"
tools:layout_editor_absoluteX="11dp"
tools:layout_editor_absoluteY="225dp"
android:id="@+id/imageButton"
android:tint="@color/colorAccent" />
如果使用的是Andorid5.0以上的原生支持,可以直接使用android:src=“”
来完成对应的展现。
如何使用AnimatedVectorDrawable?
AnimatedVectorDrawable其实是针对Vector里面的各种元素,提供了属性动画的支持方式,从而完成对应的动画效果。
借用官方文档里的例子,如果采用多个xml来实现:
VectorDrawable's XML file: vd.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="64dp"
android:width="64dp"
android:viewportHeight="600"
android:viewportWidth="600" >
<group
android:name="rotationGroup"
android:pivotX="300.0"
android:pivotY="300.0"
android:rotation="45.0" >
<path
android:name="vectorPath"
android:fillColor="#000000"
android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
</group>
</vector>
AnimatedVectorDrawable's XML file: avd.xml
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/vd" >
<target
android:name="rotationGroup"
android:animation="@anim/rotation" />
</animated-vector>
Animator XML file that is used in the AnimatedVectorDrawable's XML file: rotation.xml
<objectAnimator
android:duration="6000"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="360" />
最后在布局中的使用方法,跟使用VectorDrawable是一样的:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/avd"/>
</android.support.constraint.ConstraintLayout>
当然到此为止还有最重要的一步,否则看到的还只是静态的矢量图形:
ImageView demoView = (ImageView) findViewById(R.id.imageview);
if(demoView.getDrawable() instanceof Animatable){
((Animatable) demoView.getDrawable()).start();
}
上面是多xml的例子,相当于矢量图形、动画目标、动画规则是分开的。还有一种方式是用单一的xml文件来描述整个矢量动画资源,需要Build Tools 版本在24及以上:
<?xml version="1.0" encoding="utf-8"?>
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="600"
android:viewportHeight="600">
<group
android:name="rotationGroup"
android:pivotX="300"
android:pivotY="300"
android:rotation="45.0" >
<path
android:name="vectorPath"
android:fillColor="#000000"
android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
</group>
</vector>
</aapt:attr>
<target android:name="rotationGroup">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="360"
android:duration="6000"
/>
</aapt:attr>
</target>
</animated-vector>
因为AnimatedVectorDrawable改变的是vector中各元素的属性值,所以极大的增添了动画的实现手段及效果。
不过坑依然有,当API < 21时,扩展包中的AnimatedVectorDrawableCompat会有一些限制:
Path Morphing (PathType evaluator) Used to morph one path into another path.
Path Interpolation Used to define a flexible interpolator (represented as a path) instead of the system-defined interpolators like LinearInterpolator.
Move along path The geometry object can move around, along an arbitrary path, as part of an animation.
在此限制下,最能发挥效果的pathdata路径动画基本就可以忽略了。至少在发此文章的时候以上限制还在,只能默默期待以后吧。
总结
此文只是初略的讲述了Android中矢量图形的基础使用方法,当然深入来讲其实还有很多东西,包括第三方的一些SVG库、矢量动画的一些高级用法等等。在此只是做一个用法的基本记录,同时希望也是借此机会抛砖引玉,给大家提供一个新的处理思路吧。