A Scalable Introduction to Vector Drawables
Among the many novelties brought to Android in 5.0 Lollipop, vector drawables are one of my favorite additions. They manage to both solve one of the oldest Android pain points (wide range of screen densities) and also pave the way for much better interactions in our apps.
Introduction to Vector Drawables
What exactly are vector drawables? As their name implies, vector drawables are based on vector graphics, as opposed to raster graphics.
Developers should already be familiar with raster graphics in the assortment of PNG, JPEG, and other image files that populate your Android apps.
Raster graphics describe (in some encoded form) the actual color value of each pixel of an image, whereas vector graphics contain the recipe, via a series of draw commands, to create the desired result.
To display that recipe on a screen, the system converts it back at run-time to the same pixel data that it would have gotten from a bitmap file through a process called rasterization.
With Android Lollipop, the recipes that make vector drawables are directly written in an XML format very much like their older cousins, shape drawables.
Both vector drawables and shape drawables share the same core benefit of being rendered on-demand by the system, and thus always at the right resolution. As such, contrary to other bitmap-based images, they don’t need to have extra variations based on screen-densities.
Indeed, where the world was happily spread between LDPI, MDPI and HDPI a few years ago, today we just don’t know how many ‘x’ will be able to fit in front of ‘HDPI’ (at the time of this writing we have reached XXXHDPI).
Additionally, like any other drawable, vector drawables are natively understood by the Android toolchain, which makes their use as seamless as feasible (i.e. you can reference them in layouts, styles, acquire them through
But let’s see an example of what a vector drawable looks like in all its XML glory:
<?xml version="1.0" encoding="utf-8" ?> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="96dp" android:width="96dp" android:viewportHeight="48" android:viewportWidth="48" > <group> <path android:fillColor="@color/black_primary" android:pathData="M12 36l17-12-17-12v24zm20-24v24h4V12h-4z" /> </group> </vector>
This example draws this well known media player action icon:
The format of the XML file is voluntarily modeled after another existing vector graphics file format (of which it shares the same path expression syntax): SVG.
This means that you can fairly easily reuse any SVG you might have (or that your graphic editor can produce) by massaging it into an Android vector drawable.
Motion with Animated Vector Drawables
Because vector drawables describe a recipe of what you want displayed on the screen, it’s very easy to modify this recipe on the fly to provide a wide range of effects, including animated vector drawables (represented by the
If you look back at the XML source of the media player icon, you may have noticed that our path element is actually contained inside another element called a group.
For the sake of displaying the vector drawable, this is not a very interesting element. But if you look at the following list of valid XML attributes that can be set on a group element, you should see something emerging: rotation, scaleX, scaleY, translateX, translateY.
Indeed, you probably recognized that those are the same attributes we use to manipulate our
View instances when animating them.
Animated vector drawables are actually more of a meta-type, bridging several other pieces together much like state-list drawables (and like the latter, they are also drawables themselves).
Animated vector drawables are declared using the
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/bluetooth_loading_vector" > <target android:name="circleGroup" android:animation="@anim/circle_expand" /> <target android:name="circlePath" android:animation="@anim/circle_path" /> </animated-vector>
The first thing you need to tie in is which vector drawable the animated version is going to use as a base, which is set in the
android:drawable attribute at the top level.
The rest of the file contains several different
<target/> elements. These set up which animations are run and which part of the vector drawable they run.
Using animated vector drawables and a widget like
ProgressBar means you can quickly and easily build rich spinners like this one:
When I sat down at I/O last year and saw the introduction of Material design, I was highly skeptical of animated transitions.
James and discussed this during our Material Design session at Xamarin Evolve 2014. At the time of the talk, the only facility we had been given to do animated transitions was through keyframe animations, which are incredibly clunky to maintain.
Thankfully, the introduction of vector drawables, along with the addition of a specialized evaluator allowing path morphing, changed all of this.
The new evaluator is able to understand the path definition used by a vector drawable and create intermediary versions of it. This means that given two specific paths for a vector drawable, we can use an object animator to not only animate transformations or styles as outlined above, but also the actual
pathData of the vector itself.
Now before you get too excited, it’s not a miracle evaluator. There are two very strong requirements for it to work properly:
- The path command list needs to be of the same size
- Each command in that list needs to be of the same type
Basically, the evaluator treats the path data as an array of floats extracted from each command parameter and uses that to interpolate different paths in between.
Thanks to this and the new animation aware
AnimatedStateListDrawable class, it’s very easy to create nice state transitions like this play/pause interaction:
The transition is defined in an XML file much like a traditional state list drawable, with the addition of a section declaring the motion that happens between the various state changes:
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android" android:constantSize="true"> <item android:drawable="@drawable/ic_pause" android:state_checked="true" android:id="@+id/pause_state" /> <item android:drawable="@drawable/ic_play" android:id="@+id/play_state" /> <transition android:fromId="@id/play_state" android:toId="@id/pause_state" android:reversible="true"> <animated-vector android:drawable="@drawable/ic_play"> <target android:name="d" android:animation="@anim/play_pause" /> </animated-vector> </transition> </animated-selector>
Since the framework also keeps track of the animation running for you, you don’t have to worry that any form of lifecycle animation will be scheduled or canceled automatically.
Using the same transition facilities, you can also build more complicated interactions involving a stage-like approach like this one:
As of now, vector drawables are still a Lollipop-specific feature. However, there have been signs that the support library will soon add support for vector drawables, likely announced in time for this year’s Google I/O conference.
It’s not clear if the more advanced animations capabilities will be supported, but basic rendering will still allow developers to use a scalable image format across most API levels, which is a great start.
I kept this blog short to give a very broad overview of what vector drawables are and how they can be used. For more information (including more details on how all of these new pieces fit together), I encourage you to read my previous posts on the subject:
- Scaling To Infinity
- Animating to Infinity
- Transitioning to Infinity
- Advanced Transitions with Vector Drawables