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. By Jemma Slater.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Physics-Based Animations in Android with DynamicAnimation: Getting Started
30 mins
- Getting Started
- Using Android Jetpack
- Getting to Know the Digital Duck Pond
- Why Use Physics-Based Animation?
- Understanding Fling Animations
- Setting up Your Fling Animation
- Detecting the User’s Flings
- Using Gesture Listeners
- Starting Velocity
- Setting Min and Max Values
- Canceling Animations
- Adding Friction
- Using Spring Animations
- Dragging with Spring Animations
- Applying Spring Forces
- Animating Scale Changes
- Chaining Springs
- Where to Go From Here?
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:
- What physics-based animations are and when to use them.
- Using fling animations to let the user move objects.
- Implementing spring animations and chaining them together.
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.
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.
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'
Your build.gradle will look like this:
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.
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!
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.
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.
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.
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.
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!
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.
apply
and the others.The code should currently look like this:
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!
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.
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.
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:
- 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. - 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. -
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.
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
andDAMPING_RATIO_NO_BOUNCY
. The default isDAMPING_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
andSTIFFNESS_VERY_LOW
. The default isSTIFFNESS_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.
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.
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.
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.
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.
Go forth and add physics-based animations to your apps! If you have any comments or questions, please join the forum discussion below.