Android Studio / Drawable / VectorDrawable

Vectors For All (almost)

Regular readers of Styling Android will know of my love of VectorDrawable and AnimatedVectorDrawable. While (at the time of writing) we’re still waiting for VectorDrawableCompat so we can only use them on API 21 (Lollipop) and later. However, the release of Android Studio 1.4 has just added some backwards compatibility to the build tools so we can actually begin to use VectorDrawable for pre-Lollipop. In this article we’ll take a look at how this works.

Before we begin let’s have a quick recap of what VectorDrawable is. Essentially it is an Android wrapper around SVG path data. SVG paths are a way of specifying complex graphical elements in a declarative way. They are particularly suited to line drawings and vector graphics, and unsuitable for photographic images. Traditionally in Android we have ShapeDrawable where we can do some basic stuff but often we have to convert vector and line graphics in to bitmaps at various pixel densities in order to use them.

Android Studio 1.4 introduces the ability to import SVG graphics into Android Studio and converts them automatically to VectorDrawable. These can be icons from the material icons pack or standalone SVG files. Importing material icons works flawlessly and provides a large and rich set of icons. However, importing standalone SVG files can be rather more problematic. The reason for this is that the VectorDrawable format only supports a subset of SVG and is missing features such as gradient and pattern fills, local IRI references (the ability to give an element a unique reference and re-use it within the SVG via that reference), and transformations – which are all commonly used.

For example, even a relatively simple image such as the official SVG logo (below) fails to import because it uses local IRI references:

svg_logo

It’s currently unclear whether these omissions are for performance reasons (for example, gradients can be complex to render) or are future developments.

With an understanding the SVG format (which is beyond the scope of this article) it is possible to manually tweak the logo to remove the local IRI references and this is identical to the one above:

svg_logo2

This still does not import and the error message of “premature end of file” gives little clue where the problem lies. Thanks to a suggestion from Wojtek Kaliciński I changed the width and height from percentage values to absolute values and the import now worked. However because translations are not supported all of the elements were positioned badly:

svg_logo2

By manually applying the all of the translation and rotation transformations from the original file (by wrapping elements in elements which support transformations) I was able to actually get the official SVG logo to import and render correctly as a VectorDrawable on Marshmallow:

SVGLogo

There is a conversion tool by Juraj Novák which will convert SVG directly to VectorDrawable. It has many of the same restrictions of not handling gradients and local IRI references, but does a much better job of converting my hand-tweaked SVG. It was not thrown by having percentage width and height values, and it has an experimental mode to apply transformations which worked well in this case. But the need to manually convert the local IRI references still required hand-tweaking of the raw SVG files.

By dropping this in our res/drawable folder we can now reference it as any drawable:

<?xml version="1.0" encoding="utf-8"?>
<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:paddingBottom="@dimen/activity_vertical_margin"
  android:paddingLeft="@dimen/activity_horizontal_margin"
  android:paddingRight="@dimen/activity_horizontal_margin"
  android:paddingTop="@dimen/activity_vertical_margin"
  tools:context=".MainActivity">

  <ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:contentDescription="@null"
    android:src="@drawable/svg_logo2" />
</RelativeLayout>

Provided that we are using gradle plugin 1.4.0 or later (at the time of writing this isn’t released but 1.4.0-beta6 does the trick) this will now work back to API 1!

So what’s happening? If we take a look in the generated code in the build folder it becomes obvious:

Screen Shot 2015-10-03 at 15.20.33

For API 21 and later the XML vector drawable that we imported is used, but for earlier devices a PNG of the vector drawable is used instead.

But what if we decide that we don’t need all of these densities and are concerned about the increased size of our APK as a result of this? We can actually control which densities will be generated using the generatedDensities property of the build flavor:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.stylingandroid.vectors4all"
        minSdkVersion 7
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
        generatedDensities = ['mdpi', 'hdpi']
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.0.1'
}

If we now build (remember to clean first to remove the resources that were generated by previous builds) we can see this only creates the densities we’ve specified:

Screen Shot 2015-10-03 at 15.27.08

So, let’s now actually take a look at what is produced in the png representation:

svg_logo2

This is essentially the same as how the imported SVG was rendered before I manually added the missing transformations. I should mention that there is a lint warning which indicates that elements are not supported for raster image generation, but that does not detract from the fact that VectorDrawable is an Android-specific format so not fully supporting it seems baffling.

We now beginning to understand why transformations are not supported by the import tool – because transformations on VectorDrawable elements is not supported when converting VectorDrawable to a raster image for backwards compatibility. This would appear to be a major omission: Totally valid VectorDrawable assets which render perfectly under Lollipop and later do not actually render correctly when converted to PNG.

To, to summarise: If you use these new tools to import assets from the material icons library they work flawlessly. However, it seems misleading to even claim that the import tool is actually capable of importing SVG when it only supports a very limited subset, and will not correctly import most real-world SVG files. Moreover the lack of support even for the full VectorDrawable specification in the VectorDrawable -> raster image conversion makes the implementation feel unfinished and not really ready for general use.

For the level of manual tweaking that I was required to do to even get the official SVG logo to even be converted to a VectorDrawable by the import tool it would not have required much more work to manually convert it to a VectorDrawable and completely bypass the import tool altogether. Although I would still be required to manually apply my transformations to all of the coordinates within the SVG pathData elements in order to manually apply the necessary transformations.

Let’s hope that some of these issues are addressed soon so that these new potentially very useful new tools begin to fulfil some of their promise.

The source code for this article is available here.

© 2015, 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.

6 Comments

  1. Mark,
    Are the things same in 2016 when it comes to VectorDrawables in Android?
    With the release of Support Library 23.2 today, the support for VectorDrawables and AnimatedVectorDrawables were added for pre-Lollipop devices but does that mean we still only have option to use vectordrawables without gradient and patterns support?

    1. VectorDrawable != SVG. Currently the VectorDrawable specification does not support gradients and patterns – only SVG path data. The support library implementation brings VectorDrawable support to earlier version of Android, it does not add to the VectorDrawable specification in any way.

  2. Hello Mark,

    As I ran into difficulties importing my SVG-files (nothing special, just the plain Android bot image) into Android Studio 1.5 using the Vector Asset Studio, I first upgraded my Android Studio to 2.0 Preview, as was advised in another article I found. As that didn’t solve my problems either I eventually bumped into your article. Using the Android SVG to VectorDrawable you linked in your article, I finally managed to convert my SVG into a drawable that I can now use through my App. Thanks for that!

    However, even though I’m using Gradle version 2.8, the PNGs for backward compatibility with pre-21 APIs aren’t building. I changed the build.gradle to include the “generatedDensities” instruction as per your figure above, but this didn’t help either. What am I doing wrong? I’m pretty new to App development, so it could be I’m overlooking something rather obvious. Anyway, thanks in advance!

    Cheers,
    Alexander.

    1. The most likely cause is that you’re using an old version of the Android gradle plugin.

      That said, this approach is no longer necessary. With the release of VectorDrawableCompat there’s no need for this build-time conversion at all. I have a blog post publishing this coming Friday (18th March 2016) which goes in to a bit more detail.

      1. Thanks! I’ll have a look at your latest post and respond with some remarks there! 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.