Today I am open sourcing the first alpha release of an animation library I’ve been writing named Kyrie. Think of it as a superset of Android’s VectorDrawable and AnimatedVectorDrawable classes: it can do everything they can do and more.

Motivation

Let me start by explaining why I began writing this library in the first place.

If you read my blog post on icon animations, you know that VectorDrawable s are great because they provide density independence—they can be scaled arbitrarily on any device without loss of quality. AnimatedVectorDrawable s make them even more awesome, allowing us to animate specific properties of a VectorDrawable in a variety of ways.

However, these two classes also have several limitations:

They can’t be dynamically created at runtime (they must be inflated from a drawable resource).

They can’t be paused, resumed, or seeked.

They only support a small subset of features that SVGs provide on the web.

I started writing Kyrie in an attempt to address these problems.

Examples

Let’s walk through a few examples from the sample app that accompanies the library.

The first snippet of code below shows how we can use Kyrie to transform an existing AnimatedVectorDrawable resource into a KyrieDrawable that can be scrubbed with a SeekBar :

KyrieDrawable drawable = KyrieDrawable . create ( context , R . drawable . avd_heartbreak ); seekBar . setOnSeekBarChangeListener ( new SeekBar . OnSeekBarChangeListener () { @Override public void onProgressChanged ( SeekBar seekBar , int progress , boolean fromUser ) { long totalDuration = drawable . getTotalDuration (); drawable . setCurrentPlayTime (( long ) ( progress / 100 f * totalDuration )); } /* ... */ });

The video in Figure 1 shows the resulting animation. We can pause/resume the animation by calling pause() and resume() respectively, and can also listen to animation events using a KyrieDrawable.Listener . In the future, I plan to add a couple more features as well, such as the ability to customize the playback speed and/or play the animation in reverse.

Figure 1 - Creating a seekable animation from an existing AnimatedVectorDrawable resource (source code). Click to play.

We can also create KyrieDrawable s dynamically at runtime using the builder pattern. KyrieDrawable s are similar to SVGs and VectorDrawable s in that they are tree-like structures built of Node s. As we build the tree, we can optionally assign Animation s to the properties of each Node to create a more elaborate animation. The code below shows how we can create a path morphing animation this way:

// Fill colors. int hippoFillColor = ContextCompat . getColor ( context , R . color . hippo ); int elephantFillColor = ContextCompat . getColor ( context , R . color . elephant ); int buffaloFillColor = ContextCompat . getColor ( context , R . color . buffalo ); // SVG path data objects. PathData hippoPathData = PathData . parse ( getString ( R . string . hippo )); PathData elephantPathData = PathData . parse ( getString ( R . string . elephant )); PathData buffaloPathData = PathData . parse ( getString ( R . string . buffalo )); KyrieDrawable drawable = KyrieDrawable . builder () . viewport ( 409 , 280 ) . child ( PathNode . builder () . strokeColor ( Color . BLACK ) . strokeWidth ( 1 f ) . fillColor ( Animation . ofArgb ( hippoFillColor , elephantFillColor ). duration ( 300 ), Animation . ofArgb ( buffaloFillColor ). startDelay ( 600 ). duration ( 300 ), Animation . ofArgb ( hippoFillColor ). startDelay ( 1200 ). duration ( 300 )) . pathData ( Animation . ofPathMorph ( Keyframe . of ( 0 , hippoPathData ), Keyframe . of ( 0.2f , elephantPathData ), Keyframe . of ( 0.4f , elephantPathData ), Keyframe . of ( 0.6f , buffaloPathData ), Keyframe . of ( 0.8f , buffaloPathData ), Keyframe . of ( 1 , hippoPathData )) . duration ( 1500 ))) . build ();

Figure 2 shows the resulting animation. Note that Animation s can also be constructed using Keyframe s, just as we would do so with a PropertyValuesHolder .

Figure 2 - Creating a path morphing animation using keyframes (source code). Click to play.

Kyrie also supports animating along a path using the Animation#ofPathMotion method. Say, for example, we wanted to recreate the polygon animations from Nick Butcher’s Playing with Paths blog post (the full source code is available in the sample app):

KyrieDrawable . Builder builder = KyrieDrawable . builder (). viewport ( 1080 , 1080 ); // Draw each polygon using a PathNode with a custom stroke color. for ( Polygon polygon : polygons ) { builder . child ( PathNode . builder () . pathData ( PathData . parse ( polygon . pathData )) . strokeWidth ( 4 f ) . strokeColor ( polygon . color )); } // Animate a black dot along each polygon's perimeter. for ( Polygon polygon : polygons ) { PathData pathData = PathData . parse ( TextUtils . join ( " " , Collections . nCopies ( polygon . laps , polygon . pathData ))); Animation < PointF , PointF > pathMotion = Animation . ofPathMotion ( PathData . toPath ( pathData )). duration ( 7500 ); builder . child ( CircleNode . builder () . fillColor ( Color . BLACK ) . radius ( 8 ) . centerX ( pathMotion . transform ( p -> p . x )) . centerY ( pathMotion . transform ( p -> p . y ))); }

The left half of Figure 3 shows the resulting animation. Note that Animation#ofPathMotion returns an Animation that computes PointF objects, where each point represents a location along the specified path as the animation progresses. In order to animate each black circle’s location along this path, we use the Animation#transform method to transform the points into streams of x/y coordinates that can be consumed by the CircleNode ’s centerX and centerY properties.

Figure 3 - Recreating the polygon animations from Nick Butcher's Playing with Paths blog post (source code). Click to play.

Future work

I have a lot of ideas on how to further improve this library, but right now I am interested in what you think. Make sure you file any feature requests you might have on GitHub! And like I said, the library is still in alpha, so make sure you report bugs too. :)