Introduction
While Android does not support SVGs (Scalable Vector Graphics) directly, Lollipop introduced a new class called VectorDrawable
, which allows designers and developers to draw assets in a similar fashion using only code.
In this article, you will learn how to create a VectorDrawable
with XML files and animate them in your projects. This is only supported for devices running Android 5.0 or above, and currently there are no support-library implementations. The source files of this tutorial can be found on GitHub.
1. Creating a Vector Drawable
The main similarity between a VectorDrawable
and a standard SVG image is that both are drawn out using a path value. While understanding how SVG paths are drawn is out of the scope of this article, official documentation can be found on the W3C website. For this article, you'll simply need to know that the path tag is where the drawing occurs. Let's take a look at the SVG file that draws out the following image:
There are five major parts to this image:
- a square for the CPU body made up of two arches
- four groups of five lines that represent the CPU's wires
The following code draws this image out as an 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>
While this may look a little overwhelming, you don't actually need to fully understand how everything is drawn out to implement a VectorDrawable
in your code. However, it should be noted that I separated each of the five sections into their own unique block in the code for readability.
The top section consists of two arches to draw out the rounded square and the sections that follow represent the bottom, top, right, and left sets of lines respectively. To turn this SVG code into a VectorDrawable
, you first need to define the vector
object in XML. The following code is taken from the vector_drawable_cpu.xml file in the sample code for this article.
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="64dp" android:width="64dp" android:viewportHeight="600" android:viewportWidth="600" > </vector>
Next, you can add in the path data. The following code is broken up into five different path tags rather than one large 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>
As you can see, each path section simply uses the pathData
attribute for drawing. You can now include the VectorDrawable
XML file as a drawable in a standard ImageView
and it will scale to any size your app requires, without needing to use any Java code.
2. Animating Vector Drawables
Now that you know how to create images using only code, it's time to have a little fun and animate them. In the following animation, you'll notice that each of the groups of wires are pulsing towards and away from the CPU.
To achieve this effect, you will need to wrap each section that you want to animate in a <group>
tag. The updated version of vector_drawable_cpu.xml then looks like this:
<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>
Next, you will want to create animators for each animation type. In this case, there is one for each group of wires for a total of four. Below is an example of the top group's animation and you will also need one for the bottom, left, and right. Each of the animator XML files can be found in the sample code.
<?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>
As you can see, the propertyName
is set to translateY
, which means the animation will move along the Y axis. The valueFrom
and valueTo
control the begin and end location. By setting repeatMode
to reverse
and repeatCount
to infinite
, the animation will loop forever as long as the VectorDrawable
is visible. The duration
of the animation is set to 250
, which is the time in milliseconds.
To apply the animations to your drawable file, you will need to create a new animated-vector
XML file to associate the animators with the VectorDrawable
groups. The following code is used to create the animated_cpu.xml file.
<?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>
When all of your XML are ready to go, you can use the animated_cpu.xml drawable in an ImageView
to display it.
<ImageView android:id="@+id/cpu" android:layout_width="64dp" android:layout_height="64dp" android:src="@drawable/animated_cpu" />
To start your animation, you will need to get an instance of the Animatable
from the ImageView
and call start
.
ImageView mCpuImageView = (ImageView) findViewById( R.id.cpu ); Drawable drawable = mCpuImageView.getDrawable(); if (drawable instanceof Animatable) { ((Animatable) drawable).start(); }
After start
has been called, the wires on the CPU image will start to move with very minimal Java code used.
3. Transforming Vector Drawables
A common use case for a VectorDrawable
is transforming one image into another, such as the action bar icon that changes from a hamburger icon into an arrow. To do this, both the source and destination paths must follow an identical format for the number of elements. For this example we will define the left and right facing arrows seen above as strings.
<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>
Next, you will need to create an initial drawable for an arrow using the path for left_arrow. In the sample code, it is called 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>
The main difference between the CPU animation and the transformation lies in the animator_left_right_arrow.xml file.
<?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>
You'll notice the valueFrom
and valueTo
properties reference the path data for the left and right arrow, the valueType
is set to pathType
and propertyName
is set to pathData
. When these are set, the animator will know to change one set of path data to the other. When the animator is finished, you need to associate the VectorDrawable
with the objectAnimator
using a new animated-vector
object.
<?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>
Finally, you'll simply need to associate the animated drawable with an ImageView
and start the animation in your Java code.
<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(); }
Conclusion
As you have seen, the VectorDrawable
class is fairly straightforward to use and allows for a lot of customization to add simple animations. While the VectorDrawable
class is currently only available for devices running Android 5.0 and above, they will be invaluable as more devices support Lollipop and future Android releases.
Comments