Animations that feel fluid and realistic tend to make user interfaces more attractive. No wonder Material Design places so much emphasis on them!
If you've ever tried creating such animations, however, you know that the simple animators and interpolators offered by the Android SDK are often not good enough. That's why recent revisions of the Android Support Library come with a physics module called Dynamic Animation.
With Dynamic Animation, you can create physics-based animations that closely resemble the movements of objects in the real world. You can also make them respond to user actions in real time. In this tutorial, I'll show you how to create a few such animations.
Prerequisites
To follow along, make sure you have the following:
- Android Studio 3.0 Canary 4 or higher
- A device or emulator running Android 4.4 or higher
1. Adding Dependencies
To be able to use Dynamic Animation in your project, you must add it as an implementation
dependency in your app
module's build.gradle file:
implementation 'com.android.support:support-dynamic-animation:26.0.0-beta2'
In this tutorial, we're going to be animating an ImageView
widget. It will, of course, have to display some images, so open the Vector Assets Studio and add the following Material icons to your project:
- sentiment neutral
- sentiment very satisfied
Here's what they look like:
For best results, I suggest you set the size of the icons to 56 x 56 dp.
2. Creating a Fling Animation
When you fling an object in the real world, you give it a large momentum. Because momentum is nothing but the product of mass and velocity, the object will initially have a high velocity. Gradually, however, thanks to friction, it slows down until it stops moving completely. Using Dynamic Animation's FlingAnimation
class, you can simulate this behavior inside your app.
For the sake of demonstration, let us now create a layout containing a flingable ImageView
widget, displaying the ic_sentiment_neutral_black_56dp icon, and a Button
widget users can press to trigger the fling animation. If you place them both inside a RelativeLayout
widget, your layout XML file will look like this:
<?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:layout_margin="16dp"><ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_sentiment_neutral_black_56dp" android:id="@+id/emoji" android:layout_centerInParent="true" /><Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Fling" android:id="@+id/flinger" android:layout_alignParentTop="true" android:layout_alignParentLeft="true" android:onClick="flingIt" /></RelativeLayout>
In the above code, you can see that the Button
widget has an onClick
attribute. By clicking on the red light-bulb icon Android Studio shows beside it, you can generate an associated on-click event handler inside your Activity
class:
public void flingIt(View view) { // more code here }
You can now create a new instance of the FlingAnimation
class using its constructor, which expects a View
object and the name of an animatable property. Dynamic Animation supports several animatable properties, such as scale, translation, rotation, and alpha.
The following code shows you how to create a FlingAnimation
instance that can animate the X-coordinate of our layout's ImageView
:
// Get a reference to the view ImageView emoji = (ImageView)findViewById(R.id.emoji); // Pass it to the constructor FlingAnimation flingAnimation = new FlingAnimation(emoji, DynamicAnimation.X);
By default, a FlingAnimation
instance is configured to use 0 pixels/second as its initial velocity. That means the animation would stop as soon as it's started. To simulate a realistic fling, you must always remember to call the setStartVelocity()
method and pass a large value to it.
Additionally, you must understand that without friction, the animation will not stop. Therefore, you must also call the setFriction()
method and pass a small number to it.
The following code configures the FlingAnimation
instance such that the ImageView
is not flung out of the bounds of the user's screen:
flingAnimation.setStartVelocity(500f); flingAnimation.setFriction(0.5f);
At this point, you can simply call the start()
method to start the animation.
flingAnimation.start();
If you run the app now and press the button, you should be able to see the fling animation.
It is worth noting that you don't specify a duration or an end value when creating a physics-based animation—the animation stops automatically when it realizes that its target object is not showing any visible movement on the user's screen.
3. Simulating Springs
Dynamic Animation allows you to easily add spring dynamics to your animations. In other words, it can help you create animations that make your widgets bounce, stretch, and squash in ways that feel natural.
To keep things simple, let's now reuse our layout's ImageView
and apply a spring-based animation to it. To allow the user to initiate the animation, however, you'll need to add another Button
widget to the layout.
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Bounce" android:layout_alignParentTop="true" android:layout_alignParentRight="true" android:onClick="bounce" />
To create a spring-based animation, you must use the SpringAnimation
class. Its constructor too expects a View
object and an animatable property. The following code creates a SpringAnimation
instance configured to animate the x-coordinate of the ImageView
:
// Get a reference to the view final ImageView emoji = (ImageView)findViewById(R.id.emoji); // Pass it to the constructor SpringAnimation springAnimation = new SpringAnimation(emoji, DynamicAnimation.X);
To control the behavior of a spring-based animation, you'll need a spring. You can create one using the SpringForce
class, which allows you to specify the resting position of the spring, its damping ratio, and its stiffness. You can think of the damping ratio as a constant that, like friction, is responsible for slowing the animation down until it stops. The stiffness, on the other hand, specifies how much force is required to stretch the spring.
If all that sounds a bit too complicated, the good news is that the SpringForce
class offers several intuitively named constants you can use to quickly configure your spring. For instance, the following code creates a spring that is both very bouncy and very flexible:
SpringForce springForce = new SpringForce(); springForce.setFinalPosition(emoji.getX()); springForce.setDampingRatio(SpringForce.DAMPING_RATIO_HIGH_BOUNCY); springForce.setStiffness(SpringForce.STIFFNESS_LOW);
In the above code, you can see that we've set the value of the final resting position of the spring to the initial X-coordinate of the ImageView
. With this configuration, you can imagine that the ImageView
is attached to a tight, invisible rubber band, which quickly pulls the ImageView
back to its original position every time it is moved.
You can now associate the spring with the SpringAnimation
instance using the setSpring()
method.
springAnimation.setSpring(springForce);
Lastly, before starting the animation, you must make sure you give it a large initial velocity using the setStartVelocity()
method.
springAnimation.setStartVelocity(2000f); springAnimation.start();
If you run the app now, you should see something like this:
4. Listening to Animation Events
An animation that's created using the Dynamic Animation library must always be started from the UI thread. You can also be sure that it will start as soon as you call the start()
method. However, it runs asynchronously. Therefore, if you want to be notified when it ends, you must attach an OnAnimationEndListener
object to it using the addEndListener()
method.
To see the listener in action, let's change the Material icon the ImageView
displays every time the spring-based animation, which we created in the previous step, starts and ends. I suggest you use the ic_sentiment_very_satisfied_black_56dp icon when the animation starts, and the ic_sentiment_neutral_black_56dp icon when it ends. The following code shows you how:
// Change icon before animation starts emoji.setImageResource( R.drawable.ic_sentiment_very_satisfied_black_56dp); // Start animation springAnimation.start(); springAnimation.addEndListener( new DynamicAnimation.OnAnimationEndListener() { @Override public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, float velocity) { // Change icon after animation ends emoji.setImageResource( R.drawable.ic_sentiment_neutral_black_56dp); } });
With the above code, the animation will look like this:
5. Animating Multiple Properties
The constructors of both the FlingAnimation
and the SpringAnimation
classes can only accept one animatable property. If you want to animate multiple properties at the same time, you can either create multiple instances of the classes, which can get cumbersome, or create a new custom property that encapsulates all your desired properties.
To create a custom animatable property, you must create a subclass of the FloatPropertyCompat
class, which has two abstract methods: setValue()
and getValue()
. As you might have guessed, you can update the values of all your desired animatable properties inside the setValue()
method. Inside the getValue()
method, however, you must return the current value of any one property only. Because of this limitation, you'll usually have to make sure that the values of the encapsulated properties are not completely independent of each other.
For example, the following code shows you how to create a custom property called scale
, which can uniformly animate both the SCALE_X
and SCALE_Y
properties of a widget:
FloatPropertyCompat<View> scale = new FloatPropertyCompat<View>("scale") { @Override public float getValue(View view) { // return the value of any one property return view.getScaleX(); } @Override public void setValue(View view, float value) { // Apply the same value to two properties view.setScaleX(value); view.setScaleY(value); } };
Now that the custom property is ready, you can use it like any other animatable property. The following code shows you how to create a SpringAnimation
object with it:
SpringAnimation stretchAnimation = new SpringAnimation(emoji, scale);
While creating an animation that uses a custom property, it is a good idea to also call the setMinimumVisibleChange()
method and pass a meaningful value to it in order to make sure that the animation doesn't consume too many CPU cycles. For our animation, which scales a widget, you can use the following code:
stretchAnimation.setMinimumVisibleChange( DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE);
Here's what the custom property animation looks like:
Conclusion
You now know the basics of working with Dynamic Animation. With the techniques you learned in this tutorial, you can create convincing physics-based animations in a matter of minutes, even if you have very little knowledge of Newtonian physics.
To learn more about Dynamic Animation, refer to the official documentation. In the meantime, check out some of our other recent posts on Android app development!
- Android SDKCreate an Intelligent App With Google Cloud Speech and Natural Language APIs
- AndroidEnsure High-Quality Android Code With Static Analysis Tools
- Android SDKServerless Apps With Firebase Cloud Functions
- AndroidHow to Solve Android’s 13 Most Common Error Messages