Table of contents



Have you ever wanted to do a bouncy animation like one of these on Android? If you have, you’re in for a treat!
Dynamic-animation is a new module introduced in revision 25.3.0 of the Android Support Library. It provides a small set of classes for making realistic physics-based view animations.
You might say “whatever, I’m just gonna slap a BounceInterpolator or an OvershootInterpolator on my animation and be good”. Well, in reality these two often don’t look that great. Of course, you could always write your own interpolator or implement a whole custom animation – but now there’s a much easier way.
Classes
At the time of writing this post, the module contains just 4 classes. Let’s take a look at their docs descriptions:
- DynamicAnimation<T extends DynamicAnimation<T>>
This class is the base class of physics-based animations.
- DynamicAnimation.ViewProperty
ViewProperty holds the access of a property of a View.
You probably remember those from typical animations:
ALPHA, ROTATION, ROTATION_X, ROTATION_Y, SCALE_X, SCALE_Y, SCROLL_X, SCROLL_Y, TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z, X, Y, Z
- SpringAnimation
SpringAnimation is an animation that is driven by a SpringForce.
- SpringForce
Spring Force defines the characteristics of the spring being used in the animation.
It has 3 key parameters:
- finalPosition
The spring’s rest position (or angle/scale). - stiffness
Docs say:Stiffness corresponds to the spring constant. The stiffer the spring is, the harder it is to stretch it, the faster it undergoes dampening.
The higher the stiffness, the faster the object will settle.
- dampingRatio
Docs say:Spring damping ratio describes how oscillations in a system decay after a disturbance.

Depending on the value, our object will:
1. Oscillate forever around the rest position.
2. Oscillate around its rest position until it settles.
3. Stop gently, as soon as possible.
4. Stop quickly and without overshoot.
SpringForce has 4 predefined float constants for both stiffness and dampingRatio, but it’s also possible to set custom values.
- finalPosition
As you can see the package is currently quite small. If you’re looking for something a bit more complex in terms of spring dynamics, take a look at Facebook’s Rebound library.
Note
DynamicAnimation doesn’t extend Animation, so you won’t be able to just replace one or use it in an AnimationSet. Don’t worry though, the whole thing is still very simple.
Boooring, let’s go already!

Examples
GitHub repository: android-springanimation-examples
Setup
To get started, add the following dependency to your module’s build.gradle:
dependencies {
compile 'com.android.support:support-dynamic-animation:25.3.0'
}
The following code is written in Kotlin (give it a try if you haven’t yet!).
Making a SpringAnimation
Well, it won’t really be generic in a programming sense, but let’s start with how every SpringAnimation is made.
- Create a SpringAnimation object for your View with a specified ViewProperty
- Create a SpringForce object and set your desired parameters (which are described above).
- Apply the created SpringForce to your SpringAnimation.
- Start the animation.
// create an animation for your view and set the property you want to animate val animation = SpringAnimation(v = view, property = SpringAnimation.X) // create a spring with desired parameters val spring = SpringForce() spring.finalPosition = 100f // can also be passed directly in the constructor spring.stiffness = SpringForce.STIFFNESS_LOW // optional, default is STIFFNESS_MEDIUM spring.dampingRatio = SpringForce.DAMPING_RATIO_HIGH_BOUNCY // optional, default is DAMPING_RATIO_MEDIUM_BOUNCY // set your animation's spring animation.spring = spring // animate! animation.start()
I moved the creation code into a simple utility function to make the code in these examples a bit more readable:
fun createSpringAnimation(view: View,
property: DynamicAnimation.ViewProperty,
finalPosition: Float,
stiffness: Float,
dampingRatio: Float): SpringAnimation {
val animation = SpringAnimation(view, property)
val spring = SpringForce(finalPosition)
spring.stiffness = stiffness
spring.dampingRatio = dampingRatio
animation.spring = spring
return animation
}
Example #1 – Position
Let’s say we have an arbitrary view positioned in the center of the screen
We want to achieve the following behavior:
- Drag the view.
- Move it around.
- Release it.
- The view springs back to its original position.

class PositionActivity : AppCompatActivity() {
private companion object Params {
val STIFFNESS = SpringForce.STIFFNESS_MEDIUM
val DAMPING_RATIO = SpringForce.DAMPING_RATIO_HIGH_BOUNCY
}
lateinit var xAnimation: SpringAnimation
lateinit var yAnimation: SpringAnimation
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_position)
// create X and Y animations for view's initial position once it's known
movingView.viewTreeObserver.addOnGlobalLayoutListener(object: ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
xAnimation = createSpringAnimation(
movingView, SpringAnimation.X, movingView.x, STIFFNESS, DAMPING_RATIO)
yAnimation = createSpringAnimation(
movingView, SpringAnimation.Y, movingView.y, STIFFNESS, DAMPING_RATIO)
movingView.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
})
var dX = 0f
var dY = 0f
movingView.setOnTouchListener { view, event ->
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
// capture the difference between view's top left corner and touch point
dX = view.x - event.rawX
dY = view.y - event.rawY
// cancel animations so we can grab the view during previous animation
xAnimation.cancel()
yAnimation.cancel()
}
MotionEvent.ACTION_MOVE -> {
// a different approach would be to change the view's LayoutParams.
movingView.animate()
.x(event.rawX + dX)
.y(event.rawY + dY)
.setDuration(0)
.start()
}
MotionEvent.ACTION_UP -> {
xAnimation.start()
yAnimation.start()
}
}
true
}
}
}
Example #2 – Rotation
There’s a rotating view on our screen which behaves like this:
- Grab the view.
- Spin it.
- Release it.
- The view spins back to its original position, again with a bounce.

class RotationActivity : AppCompatActivity() {
private companion object Params {
val INITIAL_ROTATION = 0f
val STIFFNESS = SpringForce.STIFFNESS_MEDIUM
val DAMPING_RATIO = SpringForce.DAMPING_RATIO_HIGH_BOUNCY
}
lateinit var rotationAnimation: SpringAnimation
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_rotation)
// create a rotation SpringAnimation
rotationAnimation = createSpringAnimation(
rotatingView, SpringAnimation.ROTATION,
INITIAL_ROTATION, STIFFNESS, DAMPING_RATIO)
var previousRotation = 0f
var currentRotation = 0f
rotatingView.setOnTouchListener { view, event ->
val centerX = view.width / 2.0
val centerY = view.height / 2.0
val x = event.x
val y = event.y
// angle calculation
fun updateCurrentRotation() {
currentRotation = view.rotation +
Math.toDegrees(Math.atan2(x - centerX, centerY - y)).toFloat()
}
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
// cancel so we can grab the view during previous animation
rotationAnimation.cancel()
updateCurrentRotation()
}
MotionEvent.ACTION_MOVE -> {
// save current rotation
previousRotation = currentRotation
updateCurrentRotation()
// rotate view by angle difference
val angle = currentRotation - previousRotation
view.rotation += angle
}
MotionEvent.ACTION_UP -> rotationAnimation.start()
}
true
}
}
}
Example #3 – Scale
As usual, there’s a view on our screen (it could be a photo) which has the following behavior:
- Grab it with 2 fingers.
- Do a typical pinching gesture to zoom in or out.
- Release it.
- The view scales back to its original size.

class ScaleActivity : AppCompatActivity() {
private companion object Params {
val INITIAL_SCALE = 1f
val STIFFNESS = SpringForce.STIFFNESS_MEDIUM
val DAMPING_RATIO = SpringForce.DAMPING_RATIO_HIGH_BOUNCY
}
lateinit var scaleXAnimation: SpringAnimation
lateinit var scaleYAnimation: SpringAnimation
lateinit var scaleGestureDetector: ScaleGestureDetector
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scale)
// create scaleX and scaleY animations
scaleXAnimation = createSpringAnimation(
scalingView, SpringAnimation.SCALE_X,
INITIAL_SCALE, STIFFNESS, DAMPING_RATIO)
scaleYAnimation = createSpringAnimation(
scalingView, SpringAnimation.SCALE_Y,
INITIAL_SCALE, STIFFNESS, DAMPING_RATIO)
setupPinchToZoom()
scalingView.setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_UP) {
scaleXAnimation.start()
scaleYAnimation.start()
} else {
// cancel animations so we can grab the view during previous animation
scaleXAnimation.cancel()
scaleYAnimation.cancel()
// pass touch event to ScaleGestureDetector
scaleGestureDetector.onTouchEvent(event)
}
true
}
}
private fun setupPinchToZoom() {
var scaleFactor = 1f
scaleGestureDetector = ScaleGestureDetector(this,
object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
scaleFactor *= detector.scaleFactor
scalingView.scaleX *= scaleFactor
scalingView.scaleY *= scaleFactor
return true
}
})
}
}
Note
The view’s scale value can go below 0 during the animation (i.e. if you scale it up too much before releasing).
If you look closely at the above animation, you’ll see that it flips the Android upside down for a split second. ?
Wrap-up
SpringAnimation makes it quite easy to implement some basic dynamic animations. It’s a nice option, as a little bounciness can help break that linear monotony of a generic Material application. But as with any animations – be careful not to overuse them or you might drive your users crazy!