Select your language

Animating widgets as in our previous example of the snake game can be tyring. However, there is a built-in way available in kivy which allows you to quickly animate basic properties of your widgets.

The Animation Module

The built-in animation module provides basic functions to animate widget properties. The process is simple. Simply define the variable of a property, choose a duration for the transition between the current and new state and then start the animation.

A simple example to change the size of a widget would look like this

animation = Animation(background_color = color , duration= 2)
animation.start(widget)

App to test various animations

The following app allows you to test different animations in a little sand box.

Creating the layout in kv language

The layout is quickly created. The root layout is a FloatLayout which contains the sand box and the data entry section. The object we are going to create is a button which is identified by the id animatedButton. A Popup which is defined at the bottom of the kv file is used to choose a nice color which is used to change the background color of the button.

<AnimatedLayout>:
    orientation: 'horizontal'

    FloatLayout:
        canvas.before:
            Color:
                rgba: (1,1,1,1)
            Rectangle:
                pos: self.pos
                size: self.size
        AnimatedButton:
            id: animatedButton
            pos_hint: {'center_x' : .5, 'center_y' : .5}
            size_hint: .25,.25

    BoxLayout:
        size_hint: .25, 1
        orientation: 'vertical'

        Label:
            text: 'Color'

        Button:
            text: 'choose a color'
            on_press: root.choose_color()

        Button:
            text: 'Animate Color'
            on_press: root.ids['animatedButton'].animateColor()

        Label:
            text: 'Size'

        TextInput:
            on_text: root.ids['animatedButton'].sizing = self.text

        Button:
            text: 'Animate Size'
            on_press: root.ids['animatedButton'].animateSize()

        Label:
            text: 'Position'

        GridLayout:
            cols: 2
            Label:
                size_hint_x: .25
                text: 'x'
            TextInput:
                on_text: root.ids['animatedButton'].xval = self.text
            Label:
                size_hint_x: .25
                text: 'y'
            TextInput:
                on_text: root.ids['animatedButton'].yval = self.text

        Button:
            text: 'Animate Position'
            on_press: root.ids['animatedButton'].animatePosition()

        Button:
            text: 'Animate all together'
            on_press: root.ids['animatedButton'].animateAllParallel()

        Button:
            text: 'Animate all in sequence'
            on_press: root.ids['animatedButton'].animateAllSequential()


<PopupColor>:
    BoxLayout
        orientation: 'vertical'

        ColorPicker:
            size_hint: 1, .75
            on_color: app.root.myColor = self.color

        Button:
            size_hint: 1, .25
            text: 'close me'
            on_press: root.dismiss()

 

Defining the animations in the Python file

The App returns the root widget our AnimatedLayout. All the animations are defined directly in the class AnimatedButton. 

Changing color

The function animateColor takes the selected color from the color picker and transitions it within two seconds.

Changing Size

The fuction animateSize transitions the size within five seconds. A helper function newSizeHint is used to calculate the new size_hint values.

Changing Position

And again in two seconds is the position changed in the function animatePosition. To make things a bit more interesting let's use the transition parameter. By setting it to t = in_out_back is the transition moving a bit faster in the middle and overshooting at the beginning of the motion.

The animation module offers all sorts of transitions. Feel free to play around with them.

The helper function newPosition converts the string values from the TextInput field to float. Please keep the values between 0 and 1. The coordinate system starts at the lower left corner of the sand box. x = 0.25 and y = 0.25 will position the button in the lower left corner of the sand box.

Some extras

You can run animations one after the other by joining the using the + operator or run them all in parallel by joining them via the & operator. The functions animateAllParallel and animateAllSequential demonstrate this.

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
from kivy.uix.button import Button
from kivy.animation import Animation
from kivy.uix.popup import Popup
from kivy.properties import ListProperty, NumericProperty, StringProperty

class AnimateApp (App):
    def __init__(self, **kwargs):
        super(AnimateApp, self).__init__(**kwargs)
        Window.size = 1200, 700

    def build(self):
        return AnimatedLayout()

class AnimatedLayout (BoxLayout):
    myColor = ListProperty([1,1,1,1])

    def choose_color(self):
        popup = PopupColor(title='Choose a color')
        popup.open()

class AnimatedButton (Button):
    sizing = StringProperty()
    xval = StringProperty()
    yval = StringProperty()
    new_x = NumericProperty()
    nex_y = NumericProperty()

    def newSizeHint(self):
        sizeFactor = float(self.sizing)
        return self.size_hint_x * sizeFactor, self.size_hint_y * sizeFactor

    def newPosition(self):
        self.new_x = float(self.xval)
        self.new_y = float(self.yval)

    def animateColor(self):
        color = App.get_running_app().root.myColor
        animation = Animation(background_color = color , duration= 2)
        animation.start(self)

    def animateSize(self):
        animation = Animation(size_hint = self.newSizeHint(), duration = 5)
        animation.start(self)

    def animatePosition(self):
        self.newPosition()
        animation = Animation(pos_hint = {'center_x': self.new_x, 'center_y': self.new_y}, duration = 2, t = 'in_out_back')
        animation.start(self)

    def animateAllParallel(self):
        color = App.get_running_app().root.myColor
        self.newPosition()

        animation = Animation(background_color=color, duration=2) &\
        Animation(size_hint = self.newSizeHint(), duration = 5) &\
        Animation(pos_hint={'center_x': self.new_x, 'center_y': self.new_y}, duration=2)

        animation.start(self)

    def animateAllSequential(self):
        color = App.get_running_app().root.myColor
        self.newPosition()

        animation = Animation(background_color=color, duration=2) +\
        Animation(size_hint = self.newSizeHint(), duration = 5) +\
        Animation(pos_hint={'center_x': self.new_x, 'center_y': self.new_y}, duration=2)

        animation.start(self)

class PopupColor (Popup):
    pass

if __name__ == '__main__':
    AnimateApp().run()

What now?

There are some more parameters in the animation module with which you should play around. E.g. keep animations running forever or stop them on purpose. 

You might as well improve the helper functions to make them error proof.