Welcome to our Learn At Home Sale!

Limited-time Advanced Swift & Android book bundles, plus 50% off all books

Home · Android & Kotlin Tutorials

Physics-Based Animations in Android with DynamicAnimation: Getting Started

In this tutorial, you’ll learn how to use realistic, physics-based animations like fling animations and spring animations in your Android apps.

5/5 1 Rating

Version

  • Kotlin 1.3, Android 4.4, Android Studio 3.5

There are many ways to make your app stand out. For example, you can add cool features and slick designs, or you can build a solid and robust architecture. However, a simple and effective way to give your app an edge and delight your users is by using realistic animations.

Physics-based animations might sound complicated, but this tutorial will teach you how to use Jetpack’s DynamicAnimation library to animate your views with just a few lines of code. Adding these little flourishes to your app make it feel natural to use and may surprise your users — in a good way!

In this tutorial you’ll cover:

  1. What physics-based animations are and when to use them.
  2. Using fling animations to let the user move objects.
  3. Implementing spring animations and chaining them together.
Note: This tutorial assumes a basic knowledge of Kotlin, Android and Android Studio. If you’re new to Android development or haven’t installed Android Studio, refer to these Beginning Android Development tutorials.

Getting Started

Start by downloading the starter project using the Download Materials button at the top or bottom of this tutorial.

Open the project in Android Studio and open FlingAnimationActivity.kt. You’ll notice immediately that Android Studio shows several errors in red.

Missing Imports for Animation

The error Unresolved reference: FlingAnimation means the code doesn’t recognize the FlingAnimation object type. This is because you haven’t imported Jetpack’s DynamicAnimation library, where FlingAnimation is defined.

Using Android Jetpack

Android Jetpack contains the latest libraries and advice from Google to help developers build modern, high-quality apps.

Note: Although this tutorial focuses on the DynamicAnimation library, there are many other useful libraries under the Jetpack umbrella as well. You can learn more about them on the official Android developer site.

RW jetpack logo

The DynamicAnimation library is relatively small but powerful. It contains several classes including FlingAnimation and SpringAnimation, which allow you to create seamless physics-based animations in your apps.

This tutorial will cover these classes in detail later on. First, you need to be able to access them in your code. To do this, you need to declare the DynamicAnimation library as a dependency in the starter project so you can import the classes from it to use in your code.

Open your app’s build.gradle. Add the following line to the dependencies block near the bottom of the file to declare the library:

implementation 'androidx.dynamicanimation:dynamicanimation-ktx:1.0.0-alpha03'
Note: Developers at Google are continuously improving, expanding and updating Jetpack’s libraries. You can check what the latest version of the DynamicAnimation library is in the online documentation. Remember to check for the Kotlin version of the library to follow along with this tutorial.

Your build.gradle will look like this:

Gradle file after imports

Because you edited build.gradle, you’ll need to sync. Press the Sync Now button in the banner at the top of the file.

When that’s finished, build the app again and get familiar with the files. You’ll see the code now compiles without issue and no longer has a problem resolving the reference to FlingAnimation.

Getting to Know the Digital Duck Pond

Throughout this tutorial, you may find it easiest to test on a real device. If you don’t have access to one, the emulator should be good enough to try everything out.

Build and run the app to see how it looks.

Menu Screenshot

The first screen contains three buttons, which lead to areas of the app where you’ll experiment with different types of animation.

Each screen contains digital animals playing in the pond. Look and you will find a little duck, a friendly frog on a lily pad and a couple of mother ducks with their ducklings.

At the moment, the animals are stationary. You need to liven up the pond needs a bit!

Different Menu Screenshots

By the end of this tutorial, you and your newly-acquired physics-based animation knowledge will breathe some life into the duck pond.

Why Use Physics-Based Animation?

You’re all set up and raring to go, but before you start coding, take a moment to learn why you should use physics-based animations.

RW android teacher logo

When your animations use physical principles, they seem more realistic. Users can understand and recognize them more easily.

Physics-based animation works by applying the concept of force to transitions and changes. Using the rules of the physical world to drive design decisions aligns with the ethos of the Material Design guidelines recommended by Google for designing Android apps.

Animations that use non-physics-based methods like ObjectAnimator often look clunky and disjointed. They come with fixed durations and you need to cancel and re-configure them if their target values change.

Physics-based animations mitigate this problem by using physical forces to drive the changes, creating a smoother result.

Understanding Fling Animations

Fling animations mimic the effects of friction on the animated objects. When you apply force to an object, its velocity reduces over time and the animation comes to a stop in a natural, gradual way.

This type of animation often accompanies onscreen gestures by the user, which set an initial momentum.

Build and run the app. Navigate to the fling animation screen, where you’ll see a single, static duck in the middle of a duck pond.

Your aim is to enable this duck to swim around the duck pond, helped by the gentle fling actions of the user.

Static Duck Screenshot

Setting up Your Fling Animation

In the project, open FlingAnimationActivity.kt. There are two unused variables: duckFlingAnimationX and duckFlingAnimationY. You need to initialize them to animate the duck.

In setupFlingAnimations(), add the following code:

duckFlingAnimationX = FlingAnimation(duck, DynamicAnimation.X)
duckFlingAnimationY = FlingAnimation(duck, DynamicAnimation.Y)

The FlingAnimation object takes two parameters: the view, and the property of the view you want to animate.

The code to initialize each variable here is very similar, only differing in the view property each will animate: X or Y. These properties refer to the position of the view in its container.

Now that you’ve defined FlingAnimation objects for the X and Y values of the duck, the next step is to detect user interaction to determine the initial momentum.

Detecting the User’s Flings

To achieve a user-driven fling effect on a view, your app needs to be able to detect when the user has made the fling gesture on the screen. Luckily, Android has listeners you can use to achieve this.

In the last line of onCreate(), you’ll see that the duck view already has a touch listener.

Note: This code uses Kotlin Android Extensions to access the views using synthetic properties. You can learn more about them in this Kotlin Android Extensions tutorial.

Duck Touch listener

When the user touches a view, it triggers a touch listener. The callback allows you to invoke actions before activating the touch event on the view.

The listener interface method has two parameters: the target view and a MotionEvent containing details of the touch action. In this case, the MotionEvent is passed as a parameter to the onTouchEvent() function of the GestureDetector, which you initialized earlier in onCreate().

Using Gesture Listeners

The GestureListener interface is useful for programming reactions to gestures from the user. It provides a range of methods that trigger on user actions such as scroll, long-press or single-tap.

To implement a FlingAnimation, the only important gesture is, unsurprisingly, onFling().

For this case, there’s another interface you can use called SimpleOnGestureListener. As the name implies, this is a simplified convenience class that returns false for each of the interface methods. This lets you cut back on unnecessary code and just override the relevant methods for your use case.

Note: Although this use case only requires overriding onFling(), you must also provide an implementation of onDown() to make it return true, rather than the SimpleOnGestureListener default of false.

This is because every user gesture starts with a down motion as the finger touches down on the screen. If this method returns false, the system interprets that as an intent to ignore the gesture. Returning true instead lets the system get to the gesture listeners you do care about.

Starting Velocity

With the listeners in place to detect fling events on the duck view, the next step is to animate the duck to enable him to swim around the pond on fling actions. You’ve initialized the fling animation variables to animate the X and Y properties of the duck view, so now you must add a force to move the duck.

In the overridden onFling(), add the following code before return true:

duckFlingAnimationX?.setStartVelocity(velocityX)?.start()
duckFlingAnimationY?.setStartVelocity(velocityY)?.start()

With this code, you take the velocity values from the gesture listener’s onFling() and set them as the corresponding start velocities for each fling animation. You then call start() on the animation so the duck starts moving as soon as it registers the fling.

Using this method to set the velocity from the gesture listener makes the fling animation feel authentic because it responds to the force that the user applies.

Build and run, then navigate to the fling animation screen. Try making a fling gesture on the duck image. If you fling with enough gusto you may find your duck goes flying off the edge of the screen!

Fling Without limits

This happens because there are no limits on how the animation affects the view properties. Once the animation starts, it continues until it runs out of momentum. The phone screen is not very big so, unless it was a very gentle fling, the duck is likely to animate itself to a position offscreen.

You’ll fix this in your next step.

Setting Min and Max Values

To keep the duck onscreen, you need to set a min and max value for each fling animation when you initialize it. Not only is the experience less than ideal if the duck disappears after the first fling, but adding a min and max value also helps with performance. It ensures the animation stops before it goes offscreen, which preserves CPU cycles and resources.

Return to where you initialized duckFlingAnimationX and duckFlingAnimationY in setupFlingAnimations(). Using Kotlin’s apply function, add the min and max values to both animations as inside apply:

For duckFlingAnimationX:

.apply {
  setMinValue(0f)
  setMaxValue(pond.width.toFloat() - duck.width)
}

For duckFlingAnimationY:

.apply {
  setMinValue(0f)
  setMaxValue(pond.height.toFloat() - duck.width)
}

Here, you set the min value for both properties to 0f — the top-left corner of the screen. This prevents the view from animating offscreen in the up and left directions.

You take the max values from the width and height of the pond, where pond is the ID of the view in which you want to contain the duck image.

As these max values refer to the top-left of the duck for the X and Y properties, you need to subtract the width and height of the view containing the duck to calculate the final max value. This keeps the whole duck image onscreen.

Note: If you aren’t familiar with Kotlin’s scope functions, refer to the Kotlin documentation to learn more about apply and the others.

The code should currently look like this:

Min Max for duck defined

Notice that setupFlingAnimations() is called from onCreate(). This code uses an Android KTX extension function called doOnLayout(). Using this method on the duck view ensures the view is completely drawn before retrieving its width and height to use in the max value calculations.

Build and run the app. Navigate to the fling animation screen and again try a fling gesture on the duck. This time, the duck stays visible onscreen no matter how hard you try and fling it away!

flingWithoutCancel

Canceling Animations

The duck now stays within the bounds of the screen when you fling it, but it still behaves slightly oddly. If you try flinging the duck against the edges of the screen, you’ll notice that, although the animation gets canceled in one direction, the animation on the other property continues until it runs out of momentum.

This happens because you animate the X and Y properties of the view independently. When one ends by hitting its min or max value, the property animation continues along the other axis.

To prevent this, use end listeners on the fling animations. Add the following code to the fling animation initializers in setupFlingAnimations(), after the apply blocks:

For duckFlingAnimationX:

.addEndListener { _, _, _, _ -> duckFlingAnimationY?.let { if (it.isRunning) it.cancel() } }

For duckFlingAnimationY:

.addEndListener { _, _, _, _ -> duckFlingAnimationX?.let { if (it.isRunning) it.cancel() } }

In these listeners, you’re checking if the other property animation is still running when one of the animations comes to an end. If it is, you cancel it, too.

Although OnAnimationEndListener has a few parameters, none of them apply. In this case, a simple notification that the animation has come to an end is enough.

Build and run the app, and try again to fling the duck. This time, when the duck hits the bounds of the screen, both animations will stop and the duck will no longer skid around.

Smooth Fling for duck

Adding Friction

Now that you can fling your duck, your next step is to add friction for a smoother-looking animation.

In this case, you’ll add friction to the apply blocks of both duckFlingAnimationX and duckFlingAnimationY in setupFlingAnimations():

friction = 1.5f

This code sets friction for the animations. The default value for friction is 1. The higher the value, the quicker the animation will slow down. Try experimenting with different friction values for both the X and Y animations. Remember, they don’t need to be the same!

Build and run the app each time to see how changing the friction values affects the animation.

Using Spring Animations

Now that you know how to implement fling animations, it’s time to try out the other key class contained in the DynamicAnimation library: SpringAnimation. Where fling animations are great for slowing animation gradually in one direction, spring animations work nicely in cases that need a natural bounce or a fluid feel.

Build and run. This time, navigate to the simple spring animation screen, SpringAnimationActivity.kt. The pond here is a little busier, with a friendly frog hanging out at the top left and a mother and baby duck in the middle.

SpringAnimation Screen

First, focus on the ducks. Here, the baby duckling wants to explore the pond but it needs to return to its mother when it’s done.

You can achieve this effect using spring animations. You’ll let the user pull the duckling away by dragging it with their finger. When the user releases the duckling, it springs back to its initial position next to its mother.

Dragging with Spring Animations

Detecting a drag event from the user doesn’t require a GestureListener, as in the previous fling example. Instead, you can use the MotionEvent passed through onTouch() of the touch listener directly.

In the project, open SpringAnimationActivity.kt. Look at detectDragOnBabyDuck() and you’ll see the baby_duck view has a touch listener set on it. The MotionEvents needed to achieve a drag effect on the baby duck are ACTION_DOWN, ACTION_MOVE and ACTION_UP.

Add the following code to each event in the when statement:

// 1
ACTION_DOWN -> {
  startPointX = event.x
  startPointY = event.y

  babyDuckSpringAnimationX?.cancel()
  babyDuckSpringAnimationY?.cancel()
}
// 2
ACTION_MOVE -> {
  baby_duck.x += event.x - startPointX
  baby_duck.y += event.y - startPointY
}
// 3
ACTION_UP -> {
  babyDuckSpringAnimationX?.start()
  babyDuckSpringAnimationY?.start()
}

Here’s what this code does:

  1. Here, you use ACTION_DOWN to determine the starting position of the baby duck, as this event occurs when the user initially presses down on the view. You also cancel any existing animations here, in case the user intercepts the view while a previous animation is still running.
  2. For ACTION_MOVE events, you change the position of the baby duck alongside the dragging movement of the user. This achieves the effect of dragging the view around.
  3. ACTION_UP events trigger when the user releases their finger. That’s where the spring animation starts.

Build and run. Try dragging the baby duck around the pond, and see how you can change the position of the duck with your finger.

At this stage, it’s possible to drag the duckling off the screen. Don’t worry. This won’t be an issue once you’ve implemented the spring animations, enabling the duck to bounce back as soon as you release the view.

Dragging duckling enabled without spring effect

Applying Spring Forces

To implement spring animations, you need to initialize two variables: babyDuckSpringAnimationX and babyDuckSpringAnimationY. In setupBabyDuckSpringAnimations(), add the following:

babyDuckSpringAnimationX = SpringAnimation(baby_duck, DynamicAnimation.X).apply {
  spring = SpringForce(baby_duck.x).apply {
    dampingRatio = DAMPING_RATIO_HIGH_BOUNCY
    stiffness = STIFFNESS_VERY_LOW
  }
}

babyDuckSpringAnimationY = SpringAnimation(baby_duck, DynamicAnimation.Y).apply {
  spring = SpringForce(baby_duck.y).apply {
    dampingRatio = DAMPING_RATIO_HIGH_BOUNCY
    stiffness = STIFFNESS_VERY_LOW
  }
}

This code creates new SpringAnimation objects on the baby_duck view to animate the X and Y properties, similar to the FlingAnimations you created earlier.

A key difference here is that spring animations require you to set either a SpringForce or a final position before the animation starts.

SpringForce is an object from the DynamicAnimation library that sets the characteristics of the spring effect for the animation. Available characteristics are:

  • dampingRatio: Defines the rate to reduce the number of oscillations in the animation.

    There are four options here: DAMPING_RATIO_HIGH_BOUNCY, DAMPING_RATIO_MEDIUM_BOUNCY, DAMPING_RATIO_LOW_BOUNCY and DAMPING_RATIO_NO_BOUNCY. The default is DAMPING_RATIO_MEDIUM_BOUNCY.

    If you choose to set a custom value, be sure not to set the dampness to a negative number or your app will crash. You can, however, set the damping ratio to 0f and the view will oscillate forever!

  • stiffness: Defines the strength of the spring. The stiffer the spring, the more force it has and the quicker it moves the view to the final position.

    There are four options here: STIFFNESS_HIGH, STIFFNESS_MEDIUM, STIFFNESS_LOW and STIFFNESS_VERY_LOW. The default is STIFFNESS_MEDIUM.

    You can set your own float value, but this must not be negative or your app will crash.

  • finalPosition: Sets the final position of the view after the animation.

The code above defines dampingRatio and stiffness explicitly, but passes the finalPosition in the SpringForce constructor. This is set to the initial position of the baby duck, which makes it spring back into place.

Build and run the app. Again, try dragging the baby duck around the pond. This time, the duckling bounces back to where it started when you release the view, even if you drag the view offscreen.

Spring effect on duckling

Try replacing the dampingRatio and stiffness values in the SpringForce objects to see what effect changing them has on the animation.

Animating Scale Changes

So far, you’ve learned to animate the X and Y properties, but these are not the only ways you can animate views.

Turn your focus to the frog now. You can use SpringAnimation to change the scaled size of a view when a user clicks on it. This is useful for drawing attention to a view when the user selects it, for example.

For your next step, you’ll use spring animation to change the size of the frog when the user clicks it.

Initialize frogSpringAnimationScaleX and frogSpringAnimationScaleY in setupFrogSpringAnimations():

val frogSpringForce = SpringForce().apply {
  dampingRatio = DAMPING_RATIO_HIGH_BOUNCY
  stiffness - STIFFNESS_VERY_LOW
}

frogSpringAnimationScaleX = SpringAnimation(frog, DynamicAnimation.SCALE_X).apply {
  spring = frogSpringForce
}
frogSpringAnimationScaleY = SpringAnimation(frog, DynamicAnimation.SCALE_Y).apply {
  spring = frogSpringForce
}

This creates a SpringForce with dampingRatio and stiffness and applies it to the two spring animation objects you created.

The properties of the frog view you want to animate are SCALE_X and SCALE_Y.

frogSpringForce is missing a finalPosition value. This should be set each time the user clicks, rather than when you initialize the animation, so it updates each time.

To do this, add this code to the frog click listener in onCreate():

val finalPosition = if (frogSpringAnimationScaleX?.spring?.finalPosition == 2f) 1f else 2f
frogSpringAnimationScaleX?.animateToFinalPosition(finalPosition)
frogSpringAnimationScaleY?.animateToFinalPosition(finalPosition)

This sets the finalPosition of the frog’s scale values to either 1f or 2f, depending on the previous finalPosition of the animation. The result is that each click on the frog makes it grow or shrink. Calling animateToFinalPosition() also starts the animation.

Build and run and tap on the frog. The spring animation gives the frog a nice bounce effect. Again, you can try out different dampingRatio and stiffness values here.

Scale effect using spring animation

Chaining Springs

You can tie spring animations together to achieve a chain effect, which gives the cool effect of view objects being invisibly connected. In this app, it will ensure the little ducklings stay close to their parent.

Build and run and navigate to the chained spring animation screen. Here, you’ll see a duck with two ducklings.

ChainedSpringAnimation screen

Try dragging the duck around and you’ll see the ducklings don’t move. For your next step, you’ll chain spring animations together so that both ducklings follow the mother around the pond.

In the project, open ChainedSpringAnimationActivity.kt and get familiar with the code. A lot of it is similar to the spring animation example you just worked through. There are functions to set up both baby ducks with spring animations, and a function to detect drags on the mother duck and move the view accordingly.

In the ACTION_MOVE block in detectDragOnDuck(), add the following:

babyDuck1SpringAnimationX?.animateToFinalPosition(duck.translationX)
babyDuck1SpringAnimationY?.animateToFinalPosition(duck.translationY)

This code ensures that each time the user moves the duck, the first duckling animates to a final position that has the same translation from its initial position.

Build and run. Dragging the duck this time also moves the first duckling, but there’s a duckling left behind.

To chain the animations, you need to add a listener to the first animation when you initialize. That listener triggers the babyDuck2 animations to start when the first value updates.

Add the following listeners to the apply blocks of babyDuck1SpringAnimationX and babyDuck1SpringAnimationY:

For babyDuck1SpringAnimationX:

addUpdateListener { _, value, _ -> babyDuck2SpringAnimationX?.animateToFinalPosition(value) }

For babyDuck1SpringAnimationY:

addUpdateListener { _, value, _ -> babyDuck2SpringAnimationY?.animateToFinalPosition(value) }

These listeners of type OnAnimationUpdateListener have a parameter that’s useful in this case: the current value of the animation. You pass this value through as the finalPosition for the second duckling. Then, each time the first duckling moves, the second duckling will move by the same translation.

Build and run again. This time, both ducklings follow the mother around the pond, staying in a nice chain formation with a bit of a ripple effect.

Chained Spring on ducklings and mother duck

Where to Go From Here?

Download the final project using the Download Materials button at the top or bottom of this tutorial.

You covered a lot in this tutorial, but there’s still plenty of room to play with your animations. For example, revisit the examples covered above and experiment with different values to see how they look and feel. Then try animating some different view properties, such as alpha, scale or rotation.

The full list of properties you can use to animate views is in the official Android documentation.

If you haven’t already, you can continue learning about property animations by following this tutorial on property animations in Android.

You can also use this tutorial on custom views to learn how to draw your own shapes and animate them in your apps, giving your users a unique experience.

Android Celebrate Logo

Go forth and add physics-based animations to your apps! If you have any comments or questions, please join the forum discussion below.

Average Rating

5/5

Add a rating for this content

1 rating

More like this

Contributors

Comments