Android 矢量图VectorDrawable

1. 背景

维基百科中的定义:

可缩放向量图形Scalable Vector GraphicsSVG)是一种基于可扩展标记语言(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,pathclip-path标签组成一个树状结构,如图:

  • vector标签:该标签标识该xml文件是矢量图,同时在该标签中可以设置矢量图的大小。
  • group标签:类似View中的ViewGroup,group,pathclip-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还没提,下面这两个工具供你使用。

参考

  1. Android官方文档Vector drawables overview
  2. Android矢量图(一)--VectorDrawable基础
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容