Skip to main content Link Search Menu Expand Document (external link)

Interactive animations

Animatable

Anything that should be animated inherits the Animatable class. The Animatable class provides animation logic.

Displayed below is a very simple animation setup in which we animate a circle from left to right. We do this by animating the x property of our animation object.

fun main() = application {
    program {
        // -- create an animation object
        val animation = object : Animatable() {
            var x = 0.0
            var y = 360.0
        }
        
        animation.apply {
            ::x.animate(width.toDouble(), 5000)
        }
        
        extend {
            animation.updateAnimation()
            drawer.fill = ColorRGBa.PINK
            drawer.stroke = null
            drawer.circle(animation.x, animation.y, 100.0)
        }
    }
}

Link to the full example

By using .complete() we can create sequences of property animations.

fun main() = application {
    program {
        val animation = object : Animatable() {
            var x = 0.0
            var y = 0.0
        }
        
        animation.apply {
            ::x.animate(width.toDouble(), 5000)
            ::x.complete()
            ::y.animate(height.toDouble(), 5000)
        }
        
        extend {
            animation.updateAnimation()
            drawer.fill = ColorRGBa.PINK
            drawer.stroke = null
            drawer.circle(animation.x, animation.y, 100.0)
        }
    }
}

Link to the full example

If we leave out that ::x.complete() line we will see that animations for x and y run simultaneously.

fun main() = application {
    program {
        val animation = object : Animatable() {
            var x = 0.0
            var y = 0.0
        }
        
        animation.apply {
            ::x.animate(width.toDouble(), 5000)
            ::y.animate(height.toDouble(), 5000)
        }
        
        extend {
            animation.updateAnimation()
            drawer.fill = ColorRGBa.PINK
            drawer.stroke = null
            drawer.circle(animation.x, animation.y, 100.0)
        }
    }
}

Link to the full example

For those wondering where that ::x.animate() notation comes from, those are Kotlin’s property references.

Easing

A simple trick for making animations less stiff is to specify an easing.

To demonstrate we take one of the previously shown animations and add easings.

Available Easings

fun main() = application {
    program {
        val animation = object : Animatable() {
            var x = 0.0
            var y = 0.0
        }
        
        animation.apply {
            ::x.animate(width.toDouble(), 5000, Easing.CubicInOut)
            ::y.animate(height.toDouble(), 5000, Easing.CubicInOut)
        }
        
        extend {
            animation.updateAnimation()
            drawer.fill = ColorRGBa.PINK
            drawer.stroke = null
            drawer.circle(animation.x, animation.y, 100.0)
        }
    }
}

Link to the full example

Behavioral animation

fun main() = application {
    program {
        class AnimatedCircle : Animatable() {
            var x = 0.0
            var y = 0.0
            var radius = 100.0
            var latch = 0.0
            
            fun shrink() {
                // -- first stop any running animations for the radius property
                ::radius.cancel()
                ::radius.animate(10.0, 200, Easing.CubicInOut)
            }
            
            fun grow() {
                ::radius.cancel()
                ::radius.animate(Double.uniform(60.0, 90.0), 200, Easing.CubicInOut)
            }
            
            fun jump() {
                ::x.cancel()
                ::y.cancel()
                ::x.animate(Double.uniform(0.0, width.toDouble()), 400, Easing.CubicInOut)
                ::y.animate(Double.uniform(0.0, height.toDouble()), 400, Easing.CubicInOut)
            }
            
            fun update() {
                updateAnimation()
                if (!::latch.hasAnimations) {
                    val duration = Double.uniform(100.0, 700.0).toLong()
                    ::latch.animate(1.0, duration).completed.listen {
                        val action = listOf(::shrink, ::grow, ::jump).random()
                        action()
                    }
                }
            }
        }
        
        val animatedCircles = List(5) {
            AnimatedCircle()
        }
        extend {
            drawer.fill = ColorRGBa.PINK
            drawer.stroke = null
            for (ac in animatedCircles) {
                ac.update()
                drawer.circle(ac.x, ac.y, ac.radius)
            }
        }
    }
}

Link to the full example

Looping animations

While Animatable doesn’t provide explicit support for looping animations. They can be achieved through the following pattern:

fun main() = application {
    val animation = object : Animatable() {
        var x: Double = 0.0
    }
    
    program {
        extend {
            animation.updateAnimation()
            if (!animation.hasAnimations()) {
                animation.apply {
                    ::x.animate(width.toDouble(), 1000, Easing.CubicInOut)
                    ::x.complete()
                    ::x.animate(0.0, 1000, Easing.CubicInOut)
                    ::x.complete()
                }
            }
            drawer.fill = ColorRGBa.PINK
            drawer.stroke = null
            drawer.circle(animation.x, height / 2.0, 100.0)
        }
    }
}

Link to the full example

Animatable properties

Thus far we have only worked with Double properties in our animations. However, animation is not limited to Doubles.

Any property that is a LinearType can be animated through Animatable.

fun main() = application {
    program {
        val animation = object : Animatable() {
            var color = ColorRGBa.WHITE
            var position = Vector2.ZERO
        }
        animation.apply {
            ::color.animate(ColorRGBa.PINK, 5000)
            ::position.animate(Vector2(width.toDouble(), height.toDouble()), 5000)
        }
    }
}

edit on GitHub