Android AnimatedVectorDrawable

Filed Under: Android

In this tutorial, we’ll be discussing AnimatedVectorDrawable and implementing it in various ways in our android application. Having an idea of VectorDrawables would make the below article easier to understand.

AnimatedVectorDrawable

AnimatedVectorDrawable class was introduced since API 21 and is used to animate Vector Drawables beautifully and easily.

Following are some of the things that you can perform with AnimatedVectorDrawable:

  • Rotate, Scale, Translate VectorDrawables
  • Animate the VectorDrawable such as fill color etc.
  • Draw paths and do Path Morphing

Typically to animate the Vector Drawable we define the animations using the ObjectAnimator class.

ShapeShifter is a popular tool to visually create an AnimatedVectorDrawable.

For this, we need to import a Vector Asset/ SVG file.

We’ve got a sample vector drawable xml file named ic_settings.xml as shown below:


<vector android:height="24dp" android:tint="#E5496D"
    android:viewportHeight="24.0" android:viewportWidth="24.0"
    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="#FF000000" android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
</vector>

Let’s import this in ShapeShifter to create an AnimatedVectorDrawable:

android animatedvectordrawable shapeshifter demo

Firstly, we need to add the vector drawable inside a group layer in order to add animations to it.

We’ve set the vector drawable to rotate by 270 degrees.
You must set the pivotX and pivotY about which the VectorDrawable would rotate.

The background color of the VectorDrawable isn’t always imported in the ShapeShifter correctly. So you need to set that again there.

Following are some of the properties that can be set on the AnimatedVectorDrawable:

  • rotation
  • pivotX
  • pivotY
  • scaleX
  • scaleY
  • translateX
  • translateY
  • pathData
  • fillColor
  • strokeColor
  • strokeWidth
  • strokeAlpha
  • fillAlpha
  • trimPathStart
  • trimPathEnd
  • trimPathOffset

Now from the ShapeShifter, click Export – AnimatedVectorDrawable to generate the xml version of the AnimatedVectorDrawable:

avd_settings.xml


<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt">
    <aapt:attr name="android:drawable">
        <vector
            android:name="settings_icon"
            android:width="48dp"
            android:height="48dp"
            android:viewportHeight="24"
            android:viewportWidth="24">
            <group android:name="animate_vector">
                <path
                    android:name="gear"
                    android:fillColor="#FF000000"
                    android:pathData="@string/settings_path" />
            </group>
        </vector>
    </aapt:attr>
    <target android:name="animate_vector">
        <aapt:attr name="android:animation">
            <set>
                <objectAnimator
                    android:duration="800"
                    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
                    android:propertyName="rotation"
                    android:repeatCount="infinite"
                    android:repeatMode="restart"
                    android:valueFrom="0"
                    android:valueTo="270"
                    android:valueType="floatType" />
                <objectAnimator
                    android:duration="800"
                    android:interpolator="@android:interpolator/fast_out_slow_in"
                    android:propertyName="pivotX"
                    android:valueFrom="12"
                    android:valueTo="12"
                    android:valueType="floatType" />
                <objectAnimator
                    android:duration="800"
                    android:interpolator="@android:interpolator/fast_out_slow_in"
                    android:propertyName="pivotY"
                    android:valueFrom="12"
                    android:valueTo="12"
                    android:valueType="floatType" />
            </set>
        </aapt:attr>
    </target>
</animated-vector>

We’ve set the long path data as a string resource in the strings.xml file.

The name attribute of the target element in the AnimatedVectorDrawable corresponds to the name of the group, which is animated. The animation attribute takes the animator class which is object animator here.

To make the AnimatedVectorDrawable animate forever, we’ve added a repeatCount to infinite in the ObjectAnimator tag.

Now that we have got the AnimatedVectorDrawable we can set it on our ImageView(any related view) by:
android:src="@drawable/avd_settings".

Still the animated vector drawable won’t animate!

In order to make it animate, we need to do:


Animatable animatable = imageView.getDrawable();
animatable.start();

Animatable is an interface that contains methods to handle animations on drawables.
Methods like – start(), stop(), isRunning()

Setting AnimatedVectorDrawable Programmatically


AnimatedVectorDrawableCompat animatedVectorDrawableCompat = AnimatedVectorDrawableCompat.create(this, R.drawable.avd_settings);
imageView.setImageDrawable(animatedVectorDrawableCompat);

this is the Context.

Instead of creating the whole animated vector drawable in a single xml file we can set the objector animators separately as well.

In the following section, we’ll be creating different types of AnimatedVectorDrawable

Project Structure

android animatedvectordrawable project

DON’T forget to enable vector support in your app’s build.gradle:


android {
    ...
    defaultConfig {
       ...
        vectorDrawables.useSupportLibrary = true
        ...
    }
...
}

Code

The code for the activity_main.xml layout is given below:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".MainActivity">


    <ImageView
        android:id="@+id/imgSettings"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="4dp"
        app:srcCompat="@drawable/avd_settings" />


    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fabSync"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="4dp"
        android:backgroundTint="@android:color/white" />


    <ImageView
        android:id="@+id/imgJD"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="4dp"
        app:srcCompat="@drawable/avd_jd" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fabTickCross"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:backgroundTint="@android:color/white"
        android:src="@drawable/ic_tick" />


</LinearLayout>

We’ve already seen the avd_settings.xml file.

Let’s see avd_jd.xml next.

The ic_jd.xml that we’ve written is:


<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="48dp"
    android:height="48dp"
    android:viewportHeight="48.0"
    android:viewportWidth="48.0">
    <group android:name="thing">
        <path
            android:name="Dev"
            android:pathData="M 25 10 L 12 10 M 25 10 L 25 30 M 25 30 C 20 35 15 30 12 25"
            android:strokeColor="#000"
            android:strokeWidth="1" />

        <path
            android:name="Journal"
            android:pathData="M 30 10 L 30 30 M 30 10 C 45 15 45 25 30 30"
            android:strokeColor="@android:color/holo_red_dark"
            android:strokeWidth="1" />

    </group>
</vector>

The avd_jd.xml code generated from the ShapeShifter is:


<animated-vector
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt">
    <aapt:attr name="android:drawable">
        <vector
            android:name="vector"
            android:width="148dp"
            android:height="148dp"
            android:viewportWidth="48"
            android:viewportHeight="48">
            <group android:name="journaldev">
                <group
                    android:name="group_j"
                    android:pivotX="24"
                    android:pivotY="24"
                    android:rotation="270">
                    <path
                        android:name="journal"
                        android:pathData="M 25 10 L 12 10 M 25 10 L 25 30 M 25 30 C 20 35 15 30 12 25"
                        android:strokeColor="#000000"
                        android:strokeWidth="1"/>
                </group>
            </group>
            <group
                android:name="group_d"
                android:pivotX="24"
                android:pivotY="24"
                android:translateX="10"
                android:scaleX="0.5"
                android:scaleY="0.5">
                <path
                    android:name="dev"
                    android:pathData="M 30 10 L 30 30 M 30 10 C 45 15 45 25 30 30"
                    android:strokeColor="#000000"
                    android:strokeWidth="1"/>
            </group>
        </vector>
    </aapt:attr>
    <target android:name="group_d">
        <aapt:attr name="android:animation">
            <set>
                <objectAnimator
                    android:propertyName="translateX"
                    android:duration="400"
                    android:valueFrom="10"
                    android:valueTo="0"
                    android:valueType="floatType"
                    android:interpolator="@android:interpolator/fast_out_slow_in"/>
                <objectAnimator
                    android:propertyName="scaleX"
                    android:duration="600"
                    android:valueFrom="0.5"
                    android:valueTo="1"
                    android:valueType="floatType"
                    android:interpolator="@android:interpolator/fast_out_slow_in"/>
                <objectAnimator
                    android:propertyName="scaleY"
                    android:duration="600"
                    android:valueFrom="0.5"
                    android:valueTo="1"
                    android:valueType="floatType"
                    android:interpolator="@android:interpolator/fast_out_slow_in"/>
            </set>
        </aapt:attr>
    </target>
    <target android:name="group_j">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:propertyName="rotation"
                android:startOffset="100"
                android:duration="500"
                android:valueFrom="270"
                android:valueTo="0"
                android:valueType="floatType"
                android:interpolator="@android:interpolator/fast_out_slow_in"/>
        </aapt:attr>
    </target>
    <target android:name="journal">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:propertyName="strokeColor"
                android:startOffset="600"
                android:duration="200"
                android:valueFrom="#000000"
                android:valueTo="#cc0000"
                android:valueType="colorType"
                android:interpolator="@android:interpolator/fast_out_slow_in"/>
        </aapt:attr>
    </target>
</animated-vector>


In the above XML code, we’ve done scale, animate and rotate separately on both the paths.
Notice that each path has a name property which is linked to the respective target.

The XML properties are self-explanatory. And once you get a hang of it, you can write the AnimatedVectorDrawable XML code directly without the need of ShapeShifter.

The avd_cross2tick.xml and avd_tick2cross.xml are used to transform a tickmark vector drawable into a cross vector drawable. Thus it transforms paths.
The codes for the object animators are separately put inside the animator folder inside the res directory.

You can find their complete implementations in the source code/Github repository at the end of this tutorial. We’re skipping this due to space constraints.

The code for the MainActivity.java is given below:


package com.journaldev.androidanimatedvectordrawable;

import android.graphics.drawable.Animatable;

import android.graphics.drawable.Animatable2;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.design.widget.FloatingActionButton;
import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private AnimatedVectorDrawable tickToCross, crossToTick;
    private boolean isTick = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        FloatingActionButton fabSync = findViewById(R.id.fabSync);
        FloatingActionButton fabTickCross = findViewById(R.id.fabTickCross);

        tickToCross = (AnimatedVectorDrawable) getDrawable(R.drawable.avd_tick2cross);
        crossToTick = (AnimatedVectorDrawable) getDrawable(R.drawable.avd_cross2tick);


        ImageView imgSettings = findViewById(R.id.imgSettings);
        ImageView imgJD = findViewById(R.id.imgJD);
        imgSettings.setOnClickListener(this);
        imgJD.setOnClickListener(this);

        AnimatedVectorDrawableCompat animatedVectorDrawableCompat = AnimatedVectorDrawableCompat.create(this, R.drawable.avd_sync);
        fabSync.setImageDrawable(animatedVectorDrawableCompat);
        fabSync.setOnClickListener(this);
        fabTickCross.setOnClickListener(this);

    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {

            case R.id.imgSettings:
                Animatable animatable = (Animatable) ((ImageView) view).getDrawable();
                if (animatable.isRunning())
                    animatable.stop();
                else
                    animatable.start();
                break;


            case R.id.imgJD:
                animatable = (Animatable) ((ImageView) view).getDrawable();
                if (animatable.isRunning())
                    animatable.stop();
                else
                    animatable.start();
                break;


            case R.id.fabSync:
                animatable = (Animatable) ((FloatingActionButton) view).getDrawable();
                if (animatable.isRunning())
                    animatable.stop();
                else
                    animatable.start();
                break;

            case R.id.fabTickCross:
                AnimatedVectorDrawable drawable = isTick ? tickToCross : crossToTick;

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    drawable.registerAnimationCallback(new Animatable2.AnimationCallback() {
                        @Override
                        public void onAnimationStart(Drawable drawable) {
                            super.onAnimationStart(drawable);
                        }
    
                        @Override
                        public void onAnimationEnd(Drawable drawable) {
                            super.onAnimationEnd(drawable);
                        }
                    });
                }
                FloatingActionButton fab = ((FloatingActionButton) view);
                fab.setImageDrawable(drawable);
                drawable.start();
                isTick = !isTick;
                break;

        }
    }
}


Here we’ve set the AnimatedVectorDrawable on two ImageView and two FloatingActionButtons.

In order to listen to animation start and end, we can register Animation Callbacks on the AnimatedVectorDrawable as:


drawable.registerAnimationCallback(new Animatable2.AnimationCallback() {
                    @Override
                    public void onAnimationStart(Drawable drawable) {
                        super.onAnimationStart(drawable);
                    }

                    @Override
                    public void onAnimationEnd(Drawable drawable) {
                        super.onAnimationEnd(drawable);
                    }
                });

Note: registerAnimationCallback works on Android M(23) and above only.

The output of the application in action is :

android animatedvectordrawable output

This brings an end to this tutorial. You can download the project from the link below or check out the GitHub repository for the complete XML codes of all the AnimatedVectorDrawables.

Comments

  1. Adil khan says:

    Sir I am your student. I learnt many things from tutorial. This vector Animation is not working on my devices. I used marshmallow device for testing but it is crashing on ImageView some error occurred. If you give a reply I will send you that crash in next post. Thanks in advance you are good teacher.

Leave a Reply

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

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages