One of the really exciting new features in Lollipop is the inclusion of VectorDrawable and some associated classes which provide some extremely powerful new options for adding complex vector graphics as paths (which will scale across form-factors, screen sizes, and densities much better than bitmaps), and provide some equally powerful tools to animate them. In this series we’ll take a look at them and look at some of the advantages they give us, and looks at how we can get some really impressive results from relatively small amounts of code.
First things first: a small explanation. When Styling Android first started in February 2011 the inaugural post was entitled Vector Drawables, and there has subsequently been a follow up series to this. These actually covered Shape Drawables and have since been renamed accordingly. However, I felt a brief explanation was necessary for anyone with feeling of déjà vu – this really is covering a different subject than those earlier posts.
Simply explained, vector graphics are a way of describing graphical elements using geometric shapes. They are particularly well suited to graphical elements created in packages such as Adobe Illustrator or Inkscape where simple geometric shapes may be combined in to much more complex elements. Bitmap graphics, on the other hand, define the colour value for each pixel and are particularly well suited to photographic images. The big advantage of using vector graphics (where appropriate) is that they are rendered at run time, so will automatically be rendered at the pixel density of the device on which they are being rendered – thereby giving smooth graphics irrespective of device capabilities. They are also usually much smaller than the bitmap version of the same graphic. They do, however, come with a computational overhead at run time which may be an issue for more complex graphical elements.
In Android, they have been implemented using the new VectorDrawable class which has been introduced in Lollipop (although there is a third-party back-port of VectorDrawable named MrVector which I have not tried myself, but this library does not currently include support for AnimatedVectorDrawable which we’ll be covering later in this series, although it is on the roadmap). This means that for graphical elements which are well suited to vector representation we can replace mdpi, hdpi, xhdpi, xxhdpi, and xxxhdpi bitmap versions of a specific drawable with a single VectorDrawable which will be a fraction of the size of even the mdpi bitmap version.
To demonstrate this, let’s take the following SVG file (which I found in a library for displaying SVG graphics in Android):
This file is 2265 bytes as an SVG graphic, but if we render it at a bitmap of 500×500 pixels and save as a PNG it is 13272 bytes – and we’d have to use multiple bitmaps for different pixel densities whereas we only require a single vector graphic. But SVG is not the same as a VectorDrawable, so we can’t use it directly. However, VectorDrawable supports a subset of the SVG specification so we can take some elements from the SVG file and use them in our VectorDrawable. The key components which we can use are the SVG path definitions. SVG paths are analogous with the
android.graphics.Path APIs but are defined as a string which we can use within our VectorDrawable. So let’s have a look at the SVG source:
While this looks pretty scary, we can lift the bit out that we need without having to understand the inner workings! There are some attributes in the parent
<svg> element which define the canvas and view port sizes as being 500x500px, then there’s a
<g> (group) element which defines a border which we’ll ignore. Then there is another
<g> (group) element with the id “androd” (sic) which is the Android logo itself – this is the real bit that we need. It consists of 6
<path> elements which define the head; left eye; right eye; left arm; body & legs; right arm components of the image respectively. The
fill attribute of each defines the fill colour (and we can see that all of these are green except for the two eyes which are both white), followed by a
d attribute which contains the path data. For those wanting to understand the path data specification then please refer to the SVG Path Specification, but that’s not essential because we can simply take these path specifications as they are and use them within our VectorDrawables.
So let’s create our VectorDrawable:
<?xml version="1.0" encoding="utf-8"?> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:viewportWidth="500" android:viewportHeight="500" android:width="500px" android:height="500px"> <group android:name="android"> <path android:name="head" android:fillColor="#9FBF3B" android:pathData="M301.314,83.298l20.159-29.272c1.197-1.74,0.899-4.024-0.666-5.104c-1.563-1.074-3.805-0.543-4.993,1.199L294.863,80.53c-13.807-5.439-29.139-8.47-45.299-8.47c-16.16,0-31.496,3.028-45.302,8.47l-20.948-30.41c-1.201-1.74-3.439-2.273-5.003-1.199c-1.564,1.077-1.861,3.362-0.664,5.104l20.166,29.272c-32.063,14.916-54.548,43.26-57.413,76.34h218.316C355.861,126.557,333.375,98.214,301.314,83.298" /> <path android:name="left_eye" android:fillColor="#FFFFFF" android:pathData="M203.956,129.438c-6.673,0-12.08-5.407-12.08-12.079c0-6.671,5.404-12.08,12.08-12.08c6.668,0,12.073,5.407,12.073,12.08C216.03,124.03,210.624,129.438,203.956,129.438" /> <path android:name="right_eye" android:fillColor="#FFFFFF" android:pathData="M295.161,129.438c-6.668,0-12.074-5.407-12.074-12.079c0-6.673,5.406-12.08,12.074-12.08c6.675,0,12.079,5.409,12.079,12.08C307.24,124.03,301.834,129.438,295.161,129.438" /> <path android:name="left_arm" android:fillColor="#9FBF3B" android:pathData="M126.383,297.598c0,13.45-10.904,24.354-24.355,24.354l0,0c-13.45,0-24.354-10.904-24.354-24.354V199.09c0-13.45,10.904-24.354,24.354-24.354l0,0c13.451,0,24.355,10.904,24.355,24.354V297.598z" /> <path android:name="body" android:fillColor="#9FBF3B" android:pathData="M140.396,175.489v177.915c0,10.566,8.566,19.133,19.135,19.133h22.633v54.744c0,13.451,10.903,24.354,24.354,24.354c13.451,0,24.355-10.903,24.355-24.354v-54.744h37.371v54.744c0,13.451,10.902,24.354,24.354,24.354s24.354-10.903,24.354-24.354v-54.744h22.633c10.569,0,19.137-8.562,19.137-19.133V175.489H140.396z" /> <path android:name="right_arm" android:fillColor="#9FBF3B" android:pathData="M372.734,297.598c0,13.45,10.903,24.354,24.354,24.354l0,0c13.45,0,24.354-10.904,24.354-24.354V199.09c0-13.45-10.904-24.354-24.354-24.354l0,0c-13.451,0-24.354,10.904-24.354,24.354V297.598z" /> </group> </vector>
We’ve created a parent
<vector> element which contains the width and height specifications, but we actually do this twice – for each dimension we define both the size and the viewport size (i.e. Both
viewportWidth. Having independent control over these is really useful, so it’s worth a quick explanation of what they both are.
I’ll cover the width in this explanation, but height is exactly the same, just the other dimension. The
width is the intrinsic width of the Drawable – the width it will return if an external component wants to know the Drawable‘s preferred size. In other words, if you set the width to
24dp and then place it in a ImageView which has
android:layout_width="wrap_content" then, following the layout pass, the ImageView will have a size of 24dp.
viewportWidth is the size of the canvas that we are going to draw upon in pixels. All of the
pathData uses this coordinate space. If you change the viewport dimensions then you will often have to change all of your
pathData to match.
These two size dimensions are independent of each other. The easy way to think of them is that the
width is the size reported to the outside world, whereas the
viewportWidth is the internal size that is the coordinate space of our
pathData. While this may seem a little confusing at first, the benefit is that we can easily change the size that a VectorDrawable will be rendered by altering the
height values – we don’t need to change the pathData (which wild be extremely tedious, and prone to introducing errors).
Next we have a
<group> element which contains 6 <path> elements each containing the relevant fill and path data which we have simply copied and pasted from the SVG file. I’ve added
name attributes to each of the
<path> elements to make it easier to understand what each path element represents, but we’ll use these names as the series progresses. This file is still a pretty modest 2412 bytes.
We can now use this like any drawable:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".VectorDrawablesActivity"> <ImageView android:id="@+id/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android" android:contentDescription="@null" /> </RelativeLayout>
…and if we run this we can see it rendering nicely:
On small thing worth noting: While this particular VectorDrawable renders well, I have seen some issues with some other VectorDrawables containing path data extracted from SVG files not rendering well in Android Studio (at the time of writing I’m using V1.0.2 and there is a bug report for the issue here) yet rendering perfectly on a device. So always try running on a real device if you experience problems with the Android Studio preview.
So now we can significantly reduce our APK size if we use VectorDrawables where appropriate, and this will also make apps much easier to maintain because we won’t need to add new assets when we need to support additional screen densities. However, this is not all that VectorDrawables can do for us, and in the next article we’ll look at how we can animate VectorDrawables.
The source code for this article is available here.
© 2015 – 2017, Mark Allison. All rights reserved.
Copyright © 2015 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.