1. 背景
维基百科中的定义:
可缩放向量图形(Scalable Vector Graphics,SVG)是一种基于可扩展标记语言(XML),用于描述二维向量图形的图形格式。SVG由W3C制定,是一个开放标准。
矢量图的优点
- SVG何以可以任意缩放而不会失真
- SVG文件一般都比较小,使用矢量图资源达到apk缩包的效果
- SVG占用内存非常小,性能高。但是SVG明显的缺点是没有位图表达的色彩丰富。
Android 5.0(API 级别 21)开始提供矢量图支持。对于之前版本的支持请参考矢量图向后兼容性解决方案。Android中矢量图对应的类为VectorDrawable,对于矢量动画类为AnimatedVectorDrawable,关于矢量动画这部分放在下篇介绍。
2. 矢量图使用
UI提供的矢量图或者我们通过Photoshop制作的矢量图都是为.svg
格式,在Android项目中使用,需要将其转化为xml文件,具体转化参考运行 Vector Asset Studio
转化后的xml样子如下图:
根标签为vector,第一次看上去对一大串不知何方神圣的数字和标签搞懵逼,但了解后就会发现从未如此简单。
该xml文件使用起来和普通的xml Drawable完全相同,只是它对应的VectorDrawable对象。
2.1 矢量图xml文件结构
根标签为vector,它由group
,path
和clip-path
标签组成一个树状结构,如图:
- vector标签:该标签标识该xml文件是矢量图,同时在该标签中可以设置矢量图的大小。
-
group标签:类似View中的ViewGroup,
group
,path
和clip-path
标签均可以作为其子标签,用于对group包含的部分进行scale、rotate和translate和动画 - path标签:类似绘制中的Path类,用于真正构建图形,构建方式和Path也类似
-
clip-path标签:对矢量树结构中
clip-path
同级及子节点按照指定区域进行剪切,最终呈现的是指定区域中显示的部分。
下面对这四部分详细说明:
vector标签
属性 | 说明 |
---|---|
name | 矢量图名称,起标识作用 |
width | 矢量图宽度,只在View为wrap_content时起作用,支持所有单位px,dp... |
height | 同上 |
viewportWidth | 虚拟画布的宽度,只用于绘制矢量图内容时使用 |
viewportHeight | 同上 |
tint | 通过tint可以指定该矢量图的整体表面颜色 |
tintMode | 色彩的Porter-Duff混合模式。 默认为src_in |
autoMirrored | 是否开启镜像,当为true时,图形在RTL布局时,图片会镜像显示,相当于沿中心Y轴旋转180° |
alpha | 矢量图的透明度,范围0-1,默认1不透明; |
这些属性都比较容易理解,对于tineMode这块提一下
PorterDuff是什么意思呢?PorterDuff是两个人名的组合: Thomas Porter和Tom Duff,他们1984年在ACM SIGGRAPH计算机图形学发表论文《Compositing digital images》,最早提出图形混合概念,极大地推动了图形图像学的发展,有兴趣的同学可以自行查阅资料。
注意:width,height,viewportWidth和viewportHeight必须指定,否则无法显示出图形
path标签
属性 | 说明 |
---|---|
name | path名称,标识唯一path |
pathData | path的路径数据,类似Path类中的moveTo,lineTo等 |
fillColor | 填充path的颜色,默认不填充 |
strokeColor | 画笔轨迹颜色 |
strokeWidth | 画笔轨迹宽度 |
strokeAlpha | 画笔轨迹透明度 |
fillAlpha | 填充透明度 |
trimPathStart | 从路径起始位置截断路径的比率,取值范围0-1,默认0 效果就是从开始到截断[0,x.x]的部分没有内容 |
trimPathEnd | 从路径结束位置截断路径的比率,取值范围0-1,默认1 效果就是从结束到截断[x.x,1]的部分没有内容 |
trimPathOffset | 设置路径截取时的偏移比例,取值范围0-1,默认0 相当于将开始移动到指定的x.x比例处,需与trimPathStart或者trimPathEnd结合使用 |
fillType | API 24引入该属性,取值nonZero和evenOdd,默认nonZero |
很多属性都比较熟悉,这里就只对重要难懂的属性进行讲解:
pathData
该属性是绘图核心,其使用的指令和Android中Path操作非常类似,常用指令如下,关于更多更全请参考w3 SVG Path章节
指令 | 类比Path方法 | 说明 |
---|---|---|
Mx,y | moveTo(x,y) | 移动到点(x,y) |
Lx,y | lineTo(x,y) | 直线连接至点(x,y) |
Hx | lineTo(x,原y) | 水平连接 |
Vy | lintTo(原x,y) | 垂直连接 |
Qx1,y1 x2,y2 | quadTo(x1,y1,x2,y2) | 二阶贝塞尔曲线,控制点(x1,y1),终点(x2,y2) |
Cx1,y1 x2,y2 x3,y3 | cubicTo(x1,y1,x2,y2,x3,y3) | 三阶贝赛尔曲线,控制点(x1,y1),(x2,y2),终点(x3,y3) |
Arx,ry x-axis-rotation large-arc-flag,sweep-flag x,y | 效果和arcTo类似 | 画椭圆弧型,(rx,ry)为椭圆的x轴和y轴半径,x-axis-rotation为椭圆x轴旋转角度,当前点和末尾参数点(x,y)为椭圆上的起点和终点,large-arc-flag和sweep-flag共同决定使用由起点和终点组成的哪个圆弧 |
Z(z) | close() | 终点和起始点如果可以构成封闭图形则进行连接 |
指令大小写的区别:大写指令使用的是绝对坐标,小写指令使用相对上一个点的坐标
M L H V指令代码示例:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="60dp"
android:height="60dp"
android:viewportWidth="60"
android:viewportHeight="60">
<path
android:pathData="M10,10h30v30H10z,"
android:strokeWidth="1"
android:strokeColor="#FF0000" />
</vector>
效果图:
M10,10移动到(10,10)处,h30则是水平连接至(10+30,10)点处,也就是点(40,10),v30为垂直连接至(40,10+30)处,也就是点(40,40),H10为水平连接至(10,40)处,最后通过z组成封闭图形,也就是这个正方形了。
Qx1,y1 x2,y2 二阶贝赛尔曲线
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="60dp"
android:height="60dp"
android:viewportWidth="60"
android:viewportHeight="60">
<path
android:pathData="M0,0H60V60H0Z"
android:strokeWidth="1"
android:strokeColor="#0000FF" />
<path
android:pathData="M0,30Q30,0 60,30"
android:strokeWidth="1"
android:strokeColor="#FF0000" />
<path
android:pathData="M0,60Q50,30 60,60"
android:strokeWidth="1"
android:strokeColor="#FF0000" />
</vector>
蓝色线条为绘制区域,第二个path为红色线条,第三个path为绿色线条。以第二个path为例,点(0,30)为起始点,点(60,30)为终点,控制点为(30,0),从而构成了如图的二阶贝塞尔曲线。
Cx1,y1 x2,y2 x3,y3 三阶贝塞尔曲线
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="60dp"
android:height="60dp"
android:viewportWidth="60"
android:viewportHeight="60">
<path
android:pathData="M0,0H60V60H0Z"
android:strokeWidth="1"
android:strokeColor="#0000FF" />
<path
android:pathData="M0,30C20,0 40,0 60,30"
android:strokeWidth="1"
android:strokeColor="#FF0000" />
<path
android:pathData="M0,60C10,30 50,30 60,60"
android:strokeWidth="1"
android:strokeColor="#00FF00" />
</vector>
蓝色依然是可绘制区域,第二个path为红色线条,第三个path为绿色线条,以第二个path为例,起点为(0,30),终点为(60,30),第一个控制点为(20,0),第二个控制点为(40,0),从而构成了如图美丽的三阶贝塞尔曲线。
Arx,ry x-axis-rotation large-arc-flag,sweep-flag x,y 绘制椭圆弧
知道你不理解的还是large-arc-flag和sweep-flag这两个属性,这里会进行说明,不过我们先思考个简单问题,假如知道一个椭圆上的两个点,可以确定出几个椭圆?你肯定会说两个,恭喜你答对了,当然如果这两个点为一个轴上与椭圆相交两个点时,此时两椭圆重合。
那么如果我们不画椭圆,只是画椭圆弧,理所应当伴随着弧线分为长短,共可以构建出4个弧线。对于如何区分椭圆我们通过起始点和终止点是顺时针还是逆时针即可确定,对于长弧和短弧通过一个flag即可确定,有了这两个flag我们就可以唯一确定一条弧线,到这谜底就揭晓了,arge-arc-flag,sweep-flag就是来确定唯一的弧线。结合下图更容易理解哦
参数 | 取值 | 说明 |
---|---|---|
arge-arc-flag | 1 | 大弧线 |
arge-arc-flag | 0 | 小弧线 |
sweep-flag | 1 | 顺时针 |
sweep-flag | 0 | 逆时针 |
代码示例:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="60dp"
android:height="60dp"
android:viewportWidth="60"
android:viewportHeight="60">
<!--绘制蓝色可绘制正方形区域-->
<path
android:pathData="M0,0H60V60H0Z"
android:strokeWidth="1"
android:strokeColor="#0000FF" />
<!--绘制红色短弧线-->
<path
android:pathData="M20,20A20,20 0 0,0 40,40"
android:strokeWidth="1"
android:strokeColor="#FF0000" />
<!--绘制红色长弧线-->
<path
android:pathData="M20,20A20,20 0 1,0 40,40"
android:strokeWidth="1"
android:strokeColor="#FF0000" />
<!--绘制绿色段弧线-->
<path
android:pathData="M20,20A20,20 0 0,1 40,40"
android:strokeWidth="1"
android:strokeColor="#00FF00" />
<!--绘制绿色长弧线-->
<path
android:pathData="M20,20A20,20 0 1,1 40,40"
android:strokeWidth="1"
android:strokeColor="#00FF00" />
</vector>
group标签
属性 | 说明 |
---|---|
name | group名称,唯一标识group |
rotation | group的旋转角度,默认0 |
pivotX,pivotY | scale和rotation变换中心点的x,y坐标,默认(0,0); |
scaleX,scaleY | X和Y轴方向的缩放,默认1; |
translateX,translateY | X和Y轴方向的移动距离,默认0 |
group标签可以进行放大缩小,平移,旋转操作,操作的对象就是group子标签所形成图形,该部分内容比较容易理解,就不写示例代码了。
clip-path标签
clip-path标签只有两个属性,name和pathData,pathData中定义了截取显示的区域,clip-path标签生效的范围为其所在的父标签(group或者vector)中clip-path位置以下的图形生效
示例代码:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="60dp"
android:height="60dp"
android:viewportWidth="60"
android:viewportHeight="60">
<!--绘制蓝色可绘制正方形区域-->
<path
android:pathData="M0,0H60V60H0Z"
android:strokeWidth="1"
android:strokeColor="#0000FF" />
<clip-path android:pathData="M20,20 L40,40 L20,40" />
<group>
<!--绘制正方形-->
<path
android:pathData="M20,20h20v20h-20z"
android:strokeWidth="1"
android:strokeColor="#FF0000" />
</group>
</vector>
绘制在中心位置的是正常形,clip-path构建的剪切区域为这个正方形的左下角三角形区域,看效果已正常剪切了。
将<clip-path android:pathData="M20,20 L40,40 L20,40" />
调整在vector结束标签之前或者示例中group结束标签之前,效果如图
均为达到剪切的目的,其实也是有原因的,因为vector矢量图中元素是有顺序的,且后面无法影响之前的操作。
3. 总结
对于矢量图基础部分已经介绍完了,也足以应对工作的需要,当然更炫酷的操作还是矢量图动画,这部分会在下一篇讲解,期待吧!
哦,制作和转化svg矢量图为xml还没提,下面这两个工具供你使用。
- 制作简单的矢量图网站:https://editor.method.ac/
- 转化矢量图为xml文件:AndroidStudio即可完成,具体参考https://developer.android.com/studio/write/vector-asset-studio#running