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?
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.
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.
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.