绘制矢量图形非难事——如何使用Android的VectorDrawable类
内容概述
尽管Android系统并不能够直接支持SVG(即可缩放矢量图形),但Lollipop版本却引入了一个名为VectorDrawable的新类,其允许设计人员及开发人员以纯代码方式生成类似的绘制效果。
在今天的文章中,我们将共同学习如何利用XML文件创建一个VectorDrawable,并将其以动画方式显示在自己的项目当中。这项功能只能在运行有Android 5.0或者更高版本的设备上实现,而且目前还不具备任何支持库实现。本篇教程中的相关源文件可以通过GitHub网站获取。
1. 创建Vector Drawable
从相似角度来看,VectorDrawable与标准SVG图形都是利用path值绘制完成的。不过如何利用SVG path绘制图形并不在本篇文章的探讨范围之内,大家可以点击此处从W3C网站处获取必要的说明资料。在本文当中,我们只需要了解到path标签的作用是进行图形绘制即可。让我们首先从SVG文件入手,看看以下图形是如何被绘制出来的:
这一图形共由五个主要部分所组成:
一个圆角四边形作为CPU主体,该四边形由两条拱状弧线构成。
四组各自包含五根线条的图形,用于充当CPU的外延线路。
以下代码所示为如何以SVG方式绘制以上图形:
- <?xml version= "1.0" encoding= "utf-8" ?>
-
- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
- <svg version= "1.1" xmlns= "http://www.w3.org/2000/svg" xmlns:xlink= "http://www.w3.org/1999/xlink" x= "0px" y= "0px"
- width= "512px" height= "512px" viewBox= "0 0 512 512" style= "enable-background:new 0 0 512 512;" xml:space= "preserve" >
- <path id= "cpu" d="
- M341. 087 , 157 .478c7. 417 , 0 , 13.435 , 6.018 , 13.435 , 13 .435v170. 174 c0, 7.417 - 6.018 , 13.435 - 13.435 , 13 .435H170. 913 c- 7.417 , 0 - 13.435 - 6.018 - 13.435 - 13 .435V170. 913 c0- 7.417 , 6.018 - 13.435 , 13.435 - 13 .435H341.087z
- M390. 348 , 157.478 c0- 19.785 - 16.041 - 35.826 - 35.826 - 35 .826H157.479c- 19.785 , 0 - 35.826 , 16.041 - 35.826 , 35 .826v197. 043 c0, 19.785 , 16.041 , 35.826 , 35.826 , 35 .826h197.043c19. 785 0 , 35.826 - 16.041 , 35.826 - 35 .826V157.478z
-
- M193. 304 , 408 .261V462h- 17.913 v- 53 .739H193.304z
- M264. 957 , 408 .261V462h- 17 .914v- 53 .739H264.957z
- M300. 783 , 408 .261V462h- 17 .914v- 53 .739H300.783z
- M229. 13 , 408.261 V462h- 17 .913v- 53 .739H229.13z
- M336. 609 , 408 .261V462h- 17 .914v- 53 .739H336.609z
-
- M193. 304 ,50v53.739h- 17 .913V50H193.304z
- M264. 957 , 50 v53.739h- 17 .914V50H264.957z
- M300. 783 ,50v53.739h- 17 .914V50H300.783z
- M229. 13 ,50v53.739h- 17 .913V50H229.13z
- M336. 609 ,50v53. 739 h- 17 .914V50H336.609z
-
- M408. 261 , 318 .695H462v17.914h- 53 .739V318.695z
- M408. 261 , 247 .043H462v17.914h- 53 .739V247.043z
- M408. 261 , 211.217 H462v17.913h- 53 .739V211.217z
- M408. 261 , 282 .869H462v17.914h- 53 .739V282.869z
- M408. 261 , 175 .391H462v17.913h- 53 .739V175.391z
-
- M50, 318 .695h53.739v17.914H50V318.695z
- M50, 247 .043h53.739v17.914H50V247.043z
- M50, 211 .217h53.739v17.913H50V211.217z
- M50, 282.869 h53.739v17.914H50V282.869z
- M50, 175 .391h53.739v17.913H50V175.391z" />
- </svg>
虽然看起来有点繁杂,但大家其实用不着纠结于以上代码的具体含义,而且这完全不会影响到我们接下来要进行的VectorDrawable绘制工作。不过需要强调的是,我将前面提到的五大图形组成部分在代码中作为独立的区块来处理,这是为了增强代码内容的可读性。
首先,我们需要利用两条拱形弧线来绘制出圆角四边形,而在接下来的内容中我们会探讨如何分别表现出上、下、左、右四个方位的外延线条。为了将上述SVG代码转化为VectorDrawable,大家首先需要在XML当中定义vector对象。以下代码提取自本篇文章示例代码当中的vector_drawable_cpu.xml文件。
- <vector xmlns:android= "http://schemas.android.com/apk/res/android"
- android:height= "64dp"
- android:width= "64dp"
- android:viewportHeight= "600"
- android:viewportWidth= "600" >
-
- </vector>
在此之后,大家可以向其中添加path数据。下列代码同样被拆分成了五个不同的path标签而非将其作为整体处理,这当然也是为了保证内容的可读性。
- <vector xmlns:android= "http://schemas.android.com/apk/res/android"
- android:height= "64dp"
- android:width= "64dp"
- android:viewportHeight= "600"
- android:viewportWidth= "600" >
-
- <path
- android:name= "cpu"
- android:fillColor= "#000000"
- android:pathData="
- M341. 087 , 157.478 c7. 417 , 0 , 13.435 , 6.018 , 13.435 , 13.435 v170.174c0, 7.417 - 6.018 , 13.435 - 13.435 , 13.435 H170. 913 c- 7.417 , 0 - 13.435 - 6.018 - 13.435 - 13 .435V170.913c0- 7.417 , 6.018 - 13.435 , 13.435 - 13 .435H341.087z
- M390. 348 , 157.478 c0- 19.785 - 16.041 - 35.826 - 35.826 - 35 .826H157.479c- 19.785 , 0 - 35.826 , 16.041 - 35.826 , 35 .826v197. 043 c0, 19.785 , 16.041 , 35.826 , 35.826 , 35 .826h197.043c19. 785 , 0 , 35.826 - 16.041 , 35.826 - 35 .826V157.478z"
- />
-
- <path
- android:name= "wires_bottom"
- android:fillColor= "#000000"
- android:pathData="
- M193. 304 , 408 .261V462h- 17.913 v- 53 .739H193.304z
- M264. 957 , 408 .261V462h- 17 .914v- 53 .739H264.957z
- M300. 783 , 408 .261V462h- 17 .914v- 53 .739H300.783z
- M229. 13 , 408.261 V462h- 17 .913v- 53 .739H229.13z
- M336. 609 , 408 .261V462h- 17 .914v- 53 .739H336.609z"
- />
-
- <path
- android:name= "wires_top"
- android:fillColor= "#000000"
- android:pathData="
- M193. 304 ,50v53.739h- 17 .913V50H193.304z
- M264. 957 , 50 v53.739h- 17 .914V50H264.957z
- M300. 783 ,50v53.739h- 17 .914V50H300.783z
- M229. 13 ,50v53.739h- 17 .913V50H229.13z
- M336. 609 ,50v53. 739 h- 17 .914V50H336.609z"
- />
-
- <path
- android:name= "wires_right"
- android:fillColor= "#000000"
- android:pathData="
- M408. 261 , 318 .695H462v17.914h- 53 .739V318.695z
- M408. 261 , 247 .043H462v17.914h- 53 .739V247.043z
- M408. 261 , 211.217 H462v17.913h- 53 .739V211.217z
- M408. 261 , 282 .869H462v17.914h- 53 .739V282.869z
- M408. 261 , 175 .391H462v17.913h- 53 .739V175.391z"
- />
-
- <path
- android:name= "wires_left"
- android:fillColor= "#000000"
- android:pathData="
- M50, 318 .695h53.739v17.914H50V318.695z
- M50, 247 .043h53.739v17.914H50V247.043z
- M50, 211 .217h53.739v17.913H50V211.217z
- M50, 282.869 h53.739v17.914H50V282.869z
- M50, 175 .391h53.739v17.913H50V175.391z"
- />
-
- </vector>
正如大家所见,每个path片段都只需要利用pathData属性进行绘制。现在我们可以将VectorDrawable XML文件作为一个可绘制对象纳入到标准ImageView当中,而且其能够根据应用程序的实际需要任意进行尺寸缩放——完全不需要再修改任何Java代码。
2. 为Vector Drawables添加动画效果
现在我们已经了解了如何以纯代码方式创建图形,接下来要做的是找点乐子——为其添加动画效果。在以下动画中,大家会发现作为延伸线路的各组线条会不断指向并远离CPU本体进行移动。
为了达到这一目标,大家需要将包含动画效果的每个片段包含在一个<group>标签当中。经过修改的vector_drawable_cpu.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= "cpu_box" >
- <path
- android:name= "cpu"
- android:fillColor= "#000000"
- android:pathData="
- M341. 087 , 157.478 c7. 417 , 0 , 13.435 , 6.018 , 13.435 , 13.435 v170.174c0, 7.417 - 6.018 , 13.435 - 13.435 , 13.435 H170. 913 c- 7.417 , 0 - 13.435 - 6.018 - 13.435 - 13 .435V170.913c0- 7.417 , 6.018 - 13.435 , 13.435 - 13 .435H341.087z
- M390. 348 , 157.478 c0- 19.785 - 16.041 - 35.826 - 35.826 - 35 .826H157.479c- 19.785 , 0 - 35.826 , 16.041 - 35.826 , 35 .826v197. 043 c0, 19.785 , 16.041 , 35.826 , 35.826 , 35 .826h197.043c19. 785 , 0 , 35.826 - 16.041 , 35.826 - 35 .826V157.478z "/>
- </group>
- <group
- android:name= "bottom" >
- <path
- android:name= "wires_bottom"
- android:fillColor= "#000000"
- android:pathData="
- M193. 304 , 408 .261V462h- 17.913 v- 53 .739H193.304z
- M264. 957 , 408 .261V462h- 17 .914v- 53 .739H264.957z
- M300. 783 , 408 .261V462h- 17 .914v- 53 .739H300.783z
- M229. 13 , 408.261 V462h- 17 .913v- 53 .739H229.13z
- M336. 609 , 408 .261V462h- 17 .914v- 53 .739H336.609z" />
- </group>
- <group android:name= "top" >
- <path
- android:name= "wires_top"
- android:fillColor= "#000000"
- android:pathData="
- M193. 304 ,50v53.739h- 17 .913V50H193.304z
- M264. 957 , 50 v53.739h- 17 .914V50H264.957z
- M300. 783 ,50v53.739h- 17 .914V50H300.783z
- M229. 13 ,50v53.739h- 17 .913V50H229.13z
- M336. 609 ,50v53. 739 h- 17 .914V50H336.609z " />
- </group>
- <group android:name= "right" >
- <path
- android:name= "wires_right"
- android:fillColor= "#000000"
- android:pathData="
- M408. 261 , 318 .695H462v17.914h- 53 .739V318.695z
- M408. 261 , 247 .043H462v17.914h- 53 .739V247.043z
- M408. 261 , 211.217 H462v17.913h- 53 .739V211.217z
- M408. 261 , 282 .869H462v17.914h- 53 .739V282.869z
- M408. 261 , 175 .391H462v17.913h- 53 .739V175.391z" />
- </group>
- <group android:name= "left" >
- <path
- android:name= "wires_left"
- android:fillColor= "#000000"
- android:pathData="
- M50, 318 .695h53.739v17.914H50V318.695z
- M50, 247 .043h53.739v17.914H50V247.043z
- M50, 211 .217h53.739v17.913H50V211.217z
- M50, 282.869 h53.739v17.914H50V282.869z
- M50, 175 .391h53.739v17.913H50V175.391z" />
- </group>
-
- </vector>
接下来,我们需要为每个动画类型创建animator文件。在本次示例中,每组线路各使用一个animator,这就意味着共需要四个animator。以下代码所示为上方线路的动画效果,大家还需要为下、左、右线路设定类似的效果。每个animator XML文件都被包含在了本项目的示例代码当中。
- <?xml version= "1.0" encoding= "utf-8" ?>
- <set xmlns:android= "http://schemas.android.com/apk/res/android" >
- <objectAnimator
- android:propertyName= "translateY"
- android:valueType= "floatType"
- android:valueFrom= "0"
- android:valueTo= "-10"
- android:repeatMode= "reverse"
- android:repeatCount= "infinite"
- android:duration= "250" />
- </set>
如大家所见,propertyName被设定为translateY,这意味着该动画将沿Y轴方向移动。而valueFrom与valueTo则控制着位移的起点与终点。通过将repeatMode设置为reverse而repeatCount设置为infinite,整个动画会一直循环下去,其效果则在VectorDrawable处体现出来。该动画的duration被设定为250,其时长单位为毫秒。
为了将该动画应用到自己的可绘制文件当中,大家需要创建一个新的animated-vector XML文件,从而将这些animator分配给各VectorDrawable组。以下代码的作用是创建该animated_cpu.xml文件。
- <?xml version= "1.0" encoding= "utf-8" ?>
- <animated-vector xmlns:android= "http://schemas.android.com/apk/res/android"
- android:drawable= "@drawable/vector_drawable_cpu" >
-
- <target
- android:animation= "@animator/pulse_top"
- android:name= "top" />
-
- <target
- android:animation= "@animator/pulse_right"
- android:name= "right" />
-
- <target
- android:animation= "@animator/pulse_left"
- android:name= "left" />
-
- <target
- android:animation= "@animator/pulse_bottom"
- android:name= "bottom" />
- </animated-vector>
当所有必要的XML文件都已经准备完成后,大家就可以将animated_cpu.xml加入到ImageView当中进行显示了。
- <ImageView
- android:id= "@+id/cpu"
- android:layout_width= "64dp"
- android:layout_height= "64dp"
- android:src= "@drawable/animated_cpu" />
要开始播放动画效果,大家需要从ImageView当中获取Animatable实例并调用start。
- ImageView mCpuImageView = (ImageView) findViewById( R.id.cpu );
- Drawable drawable = mCpuImageView.getDrawable();
- if (drawable instanceof Animatable) {
- ((Animatable) drawable).start();
- }
在start被调用之后,CPU图形当中的线路图形就会开始移动——整个过程只需要使用少量Java代码即可实现。
3. Vector Drawables的变化方式
对于VectorDrawable来说,最常见的一种使用方式就是将一个图形转化至另一个图形,例如操作栏中的图标由汉堡变成箭头。要做到这一点,源与目标path二者都必须具备同样的格式以保证元素数量上的一致。在本次示例中,我们将如前文图片所示尝试将左箭头转化为右箭头。
- <string name= "left_arrow" >M300, 70 l 0 , 70 - 70 ,- 70 0 , 0 70 ,-70z</string>
-
- <string name= "right_arrow" >M300, 70 l 0 ,- 70 70 , 70 0 , 0 - 70 ,70z</string>
接下来,大家需要利用path为left_arrow建立一个初始drawable。在示例代码中,我们将其命名为vector_drawable_left_arrow.xml。
- <vector xmlns:android= "http://schemas.android.com/apk/res/android"
- android:height= "64dp"
- android:width= "64dp"
- android:viewportHeight= "600"
- android:viewportWidth= "600" >
-
- <path
- android:name= "left_arrow"
- android:fillColor= "#000000"
- android:pathData= "@string/left_arrow" />
- /vector>
CPU动画与这里提到的图形变化示例之间,最主要的区别就体现在animator_left_right_arrow.xml文件当中。
- <?xml version= "1.0" encoding= "utf-8" ?>
- <set xmlns:android= "http://schemas.android.com/apk/res/android" >
-
- <objectAnimator
- android:duration= "1000"
- android:propertyName= "pathData"
- android:valueFrom= "@string/left_arrow"
- android:valueTo= "@string/right_arrow"
- android:valueType= "pathType"
- android:repeatMode= "reverse"
- android:repeatCount= "-1" />
-
- </set>
大家可能已经注意到了,valueFrom与valueTo两项属性会引用左箭头与右箭头的path数据,valueType被设定为pathType而propertyName则被设定为pathData。当以上设定完成之后,该animator将明确如何将一组path数据转化为另一组。当该animator结束之后,我们还需要利用新的animated-vector对象将VectorDrawable分配至objectAnimator。
- <?xml version= "1.0" encoding= "utf-8" ?>
- <animated-vector xmlns:android= "http://schemas.android.com/apk/res/android"
- android:drawable= "@drawable/vector_drawable_left_arrow" >
-
- <target
- android:name= "left_arrow"
- android:animation= "@animator/animator_left_right_arrows" />
- </animated-vector>
最后,大家还需要将该动画drawable分配至ImageView,而后在自己的Java代码中开始运行。
- <ImageView
- android:id= "@+id/left_right_arrow"
- android:layout_width= "64dp"
- android:layout_height= "64dp"
- android:layout_below= "@+id/cpu"
- android:src= "@drawable/animated_arrow" />
- mArrowImageView = (ImageView) findViewById( R.id.left_right_arrow );
- drawable = mArrowImageView.getDrawable();
- if (drawable instanceof Animatable) {
- ((Animatable) drawable).start();
- }
总结
正如大家所见,VectorDrawable类非常易于使用而且允许开发者以自定义方式实现大量简单的动画效果。尽管VectorDrawable类目前只适用于运行有Android 5.0以及更高版本的设备,但随着更多设备开始支持Lollipop及其后续Android版本,其必将发挥更为重要的作用。