FloatingMusicActionButton an AnimatedVectorDrawable implementation

Every time I open the PocketCast app I can’t help but repeatedly click on the play button to gaze at this wonderful animation.

I could watch this all day

Google even decided to place this particular animation in their Material Design Specs

Transforming the play button to a pause button signifies that the two actions are linked, and that pressing one makes the other one visible.

We thought it would be a good idea to implement this in our own radio player app.

The whole issue with making such animation is not that it’s difficult, however it requires a bit of tinkering around with different tools to get it right.
In this blog post I try and guide you through the steps I did to make this animation.


Animated Vector Drawable

Ever since API level 21 we got access to VectorDrawable and AnimatedVectorDrawable. Even better since the release of Android Support Library 23.2 we got support for older API’s.

An example of a simple VectorDrawable can look like this

<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="64dp"
android:width="64dp"
android:viewportHeight="600"
android:viewportWidth="600" >
<group
android:name="rotationGroup"
android:pivotX="300.0"
android:pivotY="300.0"
android:rotation="45.0" >
<path
android:name="v"
android:fillColor="#000000"
android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
</group>
</vector>
  • The vector-tag defines the width and height of the vector as well as the viewportHeight and -Width.
  • The group-tag can help you to separate different parts of the vector.
    You give it a name that later comes in handy when we start animating the vector.
  • The most important tag path-tag defines your actual shape with the pathData. The pathData defines clip path using the same format as “d” attribute in a SVG’s path data.
The most important thing when we want to morph different shapes into each other is that the paths should have exact same length of commands , and exact same length of parameters for each commands — Google Developers

For example following paths are compatible for morphing

<string name="before">M3,70 l 0,-70 70,70 0,0 -70,70z</string>
<string name="after">M5, 50 l 0, -80 80, 80, 0, 10 -90, 50z</string>

The pathData includes coordinates on a 2D grid. Each letter represents a command that is used to draw. Lewis McGeary could not have explained it better than I can.

Uppercase commands mean the coordinates given are absolute and lowercase means they are relative to wherever your ‘pen’ is currently at.
M3,70 : M is the moveto command and the numbers are coordinates, so this means move the ‘pen’ to position 3 on the x-axis and 70 on the y-axis, we’ll start drawing there.
l 0,-70 70,70 0,0 -70,70 : l is the lineto command, which draws straight lines, and the numbers are again coordinates. Note that this is a lowercase l therefore these coordinates are all relative.
Finally z which means closepath, it draws a straight line from where we are now, back to our starting point. — Lewis McGeary

Sketching on an old fashioned paper

In order for our animation to work we started sketching the different points on a 128 by 128 canvas.
Drawing can help a lot here. Don’t be afraid of grabbing a piece of paper and a pencil to improve your spatial awareness.

All the button states in their final state

Play Button Coordinates

You can see our play-button is composed out of 2 triangles. In order to be compatible for morphing to the pause and stop-buttons a fourth point has to be defined on the triangles. 
The point is not visible because it’s placed at (100,64) so it overlaps with another point.

play button upper part

We come up these four parts for the upper part

(44, 32)
(44, 64)
(100,64)
(100,64)
play button bottom part

Only one extra coordinate (44, 96) for the bottom part is required

(44, 96)
(44, 64)
(100,64)
(100,64)

Pause Button Coordinates

In order to obtain the full effect from play- to pause state. We have to rotate the pause button -90°. Otherwise we get another effect then intended.

wrong
correct

During the morphing we will then rotate it back to its correct position

The pause button

We come up these four parts for the upper part

(32, 40)
(32, 56)
(96, 56)
(96, 40)

We leave a 16px margin between the two pause rectangles. 
The results for the bottom part coordinates

(32  88)
(32, 72)
(96, 72)
(96, 88)

Stop Button Coordinates

For the stop button the same rules apply as with the pause-button so we rotate the object to -90°.

the stop button

The correct coordinates for the upper part

(32, 32)
(32, 64)
(96, 64)
(96, 32)

The bottom part

(32, 96)
(32, 64)
(96, 64)
(96, 96)

Android Icon Animator

A few weeks ago while browsing r/androiddev I discovered Android Icon Animator by Roman Nurik. This marvelous tool allows you to just drag and drop any SVG and it will convert the SVG into the pathData.

Not only that but it allows you to see your animation before you even wrote one letter code. This let’s you wonder why Android Studio doesn’t come with tooling this easy. Sigh

Any who, Create a simple triangle using your favorite vector creating tool (ex. Illustrator or Inkscape) and add one extra point.

Export the vector as SVG. Opening the SVG in any text editor will reveal it’s commands.
My command looked similar to this.

M XX XX L XX XX L XX XX L XX XX Z

Play to Pause Animation

Let’s use the Android Icon Animator tool to aid us in to making our first animation.

Replace the coordinates from the command above with the coordinates we found in previous step.
I recommend you to place this pathData in your strings.xml file to keep things tidy.

<!-- Play Icon -->
<string name="play_icon_upper_path_data">M 44 32 L 44 64 L 100 64 L 100 64 Z</string>
<string name="play_icon_bottom_path_data">M 44 96 L 44 64 L 100 64 L 100 64 Z</string>
<!-- Pause Icon -->
<string name="pause_icon_upper_path_data">M 32 40 L 32 56 L 96 56 L 96 40 Z</string>
<string name="pause_icon_bottom_path_data">M 32 88 L 32 72 L 96 72 L 96 88 Z</string>

Click on the icon and create a new group, name it: parts. In the parts-group create two more groups and name them: upperpart and bottompart.

Your structure should now look like this

In the upperpart-group create a path named upper.
In the bottompart-group create a path named bottom.

You should now see your vector appear on screen.

Click the New animation button. 
Name your animation play_to_pause and leave the default duration at 300 ms.
Click the upper path and click on the little stopwatch logo, select pathData.
Change the toValue to the pause-pathData.

Repeat this step for the bottom path.

Your animation is almost done.
Click on the stopwatch icon for the parts group. Click on rotation and change the toValue to 90° degrees.

You can now use the slider to see the animation in action as it will render on Android.

Use the export function to generate your initial VectorDrawable and the chosen animations.

The export function is quite messy as it returns the animation in one XML file. 
I extracted everything to the correct folders for maximum readability and re-usability.

Inside of the drawable folder paste the play_icon.xml which is our VectorDrawable

<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<group
android:name="@string/play_icon_group_parts"
android:pivotX="64"
android:pivotY="64"
android:scaleX="1"
android:scaleY="1">

<group android:name="@string/play_icon_group_top">
<path
android:name="@string/play_icon_top_path_name"
android:pathData="@string/play_icon_upper_path_data"
android:fillColor="@android:color/black"
android:strokeLineCap="butt"
android:strokeLineJoin="miter"
android:strokeMiterLimit="10"/>
</group>
<group android:name="@string/play_icon_group_bottom">
<path
android:name="@string/play_icon_bottom_path_name"
android:pathData="@string/play_icon_bottom_path_data"
android:fillColor="@android:color/black"
android:strokeLineCap="butt"
android:strokeLineJoin="miter"
android:strokeMiterLimit="10"/>
</group>

</group>
</vector>

Make a play_to_pause_animation.xml which will be our actual AnimatedVectorDrawable

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

<target
android:name="@string/play_icon_group_parts"
android:animation="@animator/rotate_90_animation"/>

<target
android:name="@string/play_icon_top_path_name"
android:animation="@animator/upper_play_to_pause_animation"/>

<target
android:name="@string/play_icon_bottom_path_name"
android:animation="@animator/bottom_play_to_pause_animation"/>
</animated-vector>

The AnimatedVectorDrawable references the play_icon.xml and applies all the animation on the different parts

These animations are stored in the animator folder

For example the rotate_90_animation.xml animation

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@integer/animation_duration"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="90"
android:valueType="floatType"/>

If you want to check out the full source code you can take a look at the repository on Github.

Custom View

Now our radio player app is able to stream ondemands and live radio.
That means we have two possible states the icon can animate to.
It also means we can animate back to the play icon when a stream is stopped or an ondemand was paused.

I decided to make a custom FloatingActionButton that does just that.

Add the FloatingActionButton to your layout and chose your mode with app:mode. You have the option between playToPause and playToStop.

<be.rijckaert.tim.animatedvector.FloatingMusicActionButton
android:src="@drawable/play_to_pause_animation"
android:layout_width="wrap_content"
app:backgroundTint="@color/colorAccent"
app:mode="playToPause"
android:layout_height="wrap_content" />

You can change the behavior dynamically by calling

val musicFab= fab as FloatingMusicActionButton
musicFab.setMode(FloatingMusicActionButton.Mode.PLAY_TO_STOP)