AnimatedVectorDrawable / VectorDrawable

VectorDrawables – Part 2

In the previous article we looked at how to convert an existing SVG image in to a VectorDrawable which enables us to replace lots of large bitmap Drawables which can be mutch smaller and are much easier to maintain. However that’s not where the benefit of VectorDrawables end – we can also animate then and in this article we’ll look at doing precisely that by making the Android logo shrug!

The animation that we’re going to apply is a simple Y translation where we want to move the head and arms up and down whilst keeping the body motionless. On the face of it this looks pretty tricky because these are all components of a single Drawable. But there is component which was introduced along with VectorDrawable in Lollipop named AnimatedVectorDrawable which makes this really easy. In the previous article we gave each of our path components a name attribute and we can apply specific animations to different path elements within the VectorDrawable. So in this case we’d need to animate head, left_eye, right_eye, left_arm, and right_arm. This is actually tricky because individual <path> elements do not have a translateX and translateY attributes which we can control from an Object Animator whereas <group> does, so we’ll need to wrap the relevant <path> elements in <group> elements which can have translation Animations applied:

<?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">
        <group android:name="head_eyes">
            <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" />
        </group>
        <group android:name="arms">
            <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="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>
        <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" />
    </group>
</vector>

We can now define a drawable consisting of an <animated-vector> to apply animations to these groups within the main android group in order to animate some of the path elements:

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/android">

    <target
        android:animation="@animator/shrug"
        android:name="head_eyes" />

    <target
        android:animation="@animator/shrug"
        android:name="arms" />
</animated-vector>

While I am aware that I could have grouped all of the head eyes and arms elements in to a single group, I have deliberately kept them as two to show how multiple groups can be controlled within a single <animated-vector> – and there is no reason why the same animation needs to be used either – so different path elements could have different animations applied.

The outer <animated-vector> element defines the VectorDrawable that we’re animating – in this case it’s android.xml, and the inner <target> elements define specific named target within the VectorDrawable and the specific animations that we’re going to apply to each.

As we discussed with translations earlier, there are actually different things that can be animated on the <vector>, <group>, <clip-path>, and <path> elements. Take a look at the VectorDrawable JavaDocs to see what attributes are supported by each of these to know which elements you’ll need to apply specific animations. For example: applying a tint will need to performed on the <vector> element whereas modifying the fill colour will need to be performed on the individual <path> elements.

The shrug animation is just a simple, repeating Y translate animator:

<?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>

In order to run this animation we need to do a couple of things. First we need to change the drawable that we’re setting on the ImageView in our layout to the animated 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/animated_android"
        android:contentDescription="@null" />

</RelativeLayout>

If we run this we’ll just see the static image. That’s because we actually need to start the animation running – it doesn’t start automatically. We can do this in our Activity, by finding the control (with the necessary type checks) and start the Animation if the Drawable is an instance of Animatable (which AnimatedVectorDrawable implements):

public class VectorDrawablesActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_vector_drawables);
        ImageView androidImageView = (ImageView) findViewById(R.id.android);
        Drawable drawable = androidImageView.getDrawable();
        if (drawable instanceof Animatable) {
            ((Animatable) drawable).start();
        }
    }
}

If we run this we can see our animation being applied to only the specified target paths:

In the next article we’ll dive even deeper still and look at some other really cool animation techniques that can be done using AnimatedVectorDrawable.

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.

8 Comments

  1. Is VectorDrawables going into the support library? Kind of useless for the moment when its only supported by android >= 5.0.

  2. Great series! It really helps understand and play with vector drawables.

    I noticed that in your example the “eyes” of the Android robot have an explicit fill color that matches that background.

    Do you know how to make a path “subtractive”, so the eyes are actually transparent?

    1. There are ways to make the path subtractive (read up on SVG path data for examples) but that would not be correct in this case. The eyes are actually white, and not transparent. I did not hand-craft this – I imported a SVG of bugdroid and that’s how they were actually defined in the SVG.

      1. Ok, just for sake of completeness here’s what I found.

        You can define paths containing holes in them. However if you do so, the only way to move those holes is by changing the location of the holes in the path, and animating the pathData.

        When doing this though, It also seems that you have to be careful with the path length (or complexity?). It *seems* if the path length is too long, Android starts throwing ArrayIndexOutOfBoundsException. I’ll file a bug if I can nail this one down.

        Thanks again!

  3. This really is a great post and series. Thanks for sharing and articulating in such a digestible way! I may already know the answer to this, but your insight could perhaps broaden my approach: If I had a vector drawable that visually had separate elements but all encapsulated in one path data, how would you approach separating these elements so one could actually animate them individually?

    e.g. Vector Drawable below is an account icon with a plus, and I’d like to be able to animate just the plus.

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.