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.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

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.

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.

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.