Quick search

Table Of Contents

Source code for kivy.animation

'''
Animation
=========

:class:`Animation` and :class:`AnimationTransition` are used to animate
:class:`~kivy.uix.widget.Widget` properties. You must specify at least a
property name and target value. To use an Animation, follow these steps:

    * Setup an Animation object
    * Use the Animation object on a Widget

Simple animation
----------------

To animate a Widget's x or y position, simply specify the target x/y values
where you want the widget positioned at the end of the animation::

    anim = Animation(x=100, y=100)
    anim.start(widget)

The animation will last for 1 second unless :attr:`duration` is specified.
When anim.start() is called, the Widget will move smoothly from the current
x/y position to (100, 100).

Multiple properties and transitions
-----------------------------------

You can animate multiple properties and use built-in or custom transition
functions using :attr:`transition` (or the `t=` shortcut). For example,
to animate the position and size using the 'in_quad' transition::

    anim = Animation(x=50, size=(80, 80), t='in_quad')
    anim.start(widget)

Note that the `t=` parameter can be the string name of a method in the
:class:`AnimationTransition` class or your own animation function.

Sequential animation
--------------------

To join animations sequentially, use the '+' operator. The following example
will animate to x=50 over 1 second, then animate the size to (80, 80) over the
next two seconds::

    anim = Animation(x=50) + Animation(size=(80, 80), duration=2.)
    anim.start(widget)

Parallel animation
------------------

To join animations in parallel, use the '&' operator. The following example
will animate the position to (80, 10) over 1 second, whilst in parallel
animating the size to (800, 800)::

    anim = Animation(pos=(80, 10))
    anim &= Animation(size=(800, 800), duration=2.)
    anim.start(widget)

Repeating animation
-------------------

.. versionadded:: 1.8.0

.. note::
    This is currently only implemented for 'Sequence' animations.

To set an animation to repeat, simply set the :attr:`Sequence.repeat`
property to `True`::

    anim = Animation(...) + Animation(...)
    anim.repeat = True
    anim.start(widget)

For flow control of animations such as stopping and cancelling, use the methods
already in place in the animation module.
'''

__all__ = ('Animation', 'AnimationTransition')

from math import sqrt, cos, sin, pi
from kivy.event import EventDispatcher
from kivy.clock import Clock
from kivy.compat import string_types, iterkeys


[docs]class Animation(EventDispatcher): '''Create an animation definition that can be used to animate a Widget. :Parameters: `duration` or `d`: float, defaults to 1. Duration of the animation, in seconds. `transition` or `t`: str or func Transition function for animate properties. It can be the name of a method from :class:`AnimationTransition`. `step` or `s`: float Step in milliseconds of the animation. Defaults to 1 / 60. :Events: `on_start`: widget Fired when the animation is started on a widget. `on_complete`: widget Fired when the animation is completed or stopped on a widget. `on_progress`: widget, progression Fired when the progression of the animation is changing. .. versionchanged:: 1.4.0 Added s/step parameter. ''' _instances = set() __events__ = ('on_start', 'on_progress', 'on_complete') def __init__(self, **kw): super(Animation, self).__init__(**kw) # Initialize self._clock_installed = False self._duration = kw.get('d', kw.get('duration', 1.)) self._transition = kw.get('t', kw.get('transition', 'linear')) self._step = kw.get('s', kw.get('step', 1. / 60.)) if isinstance(self._transition, string_types): self._transition = getattr(AnimationTransition, self._transition) for key in ('d', 't', 's', 'step', 'duration', 'transition'): kw.pop(key, None) self._animated_properties = kw self._widgets = {} @property
[docs] def duration(self): '''Return the duration of the animation. ''' return self._duration
@property
[docs] def transition(self): '''Return the transition of the animation. ''' return self._transition
@property
[docs] def animated_properties(self): '''Return the properties used to animate. ''' return self._animated_properties
@staticmethod
[docs] def stop_all(widget, *largs): '''Stop all animations that concern a specific widget / list of properties. Example:: anim = Animation(x=50) anim.start(widget) # and later Animation.stop_all(widget, 'x') ''' if len(largs): for animation in list(Animation._instances): for x in largs: animation.stop_property(widget, x) else: for animation in set(Animation._instances): animation.stop(widget)
@staticmethod
[docs] def cancel_all(widget, *largs): '''Cancel all animations that concern a specific widget / list of properties. See :attr:`cancel`. Example:: anim = Animation(x=50) anim.start(widget) # and later Animation.cancel_all(widget, 'x') .. versionadded:: 1.4.0 ''' if len(largs): for animation in list(Animation._instances): for x in largs: animation.cancel_property(widget, x) else: for animation in set(Animation._instances): animation.cancel(widget)
[docs] def start(self, widget): '''Start the animation on a widget. ''' self.stop(widget) self._initialize(widget) self._register() self.dispatch('on_start', widget)
[docs] def stop(self, widget): '''Stop the animation previously applied to a widget, triggering the `on_complete` event.''' props = self._widgets.pop(widget.uid, None) if props: self.dispatch('on_complete', widget) self.cancel(widget)
[docs] def cancel(self, widget): '''Cancel the animation previously applied to a widget. Same effect as :attr:`stop`, except the `on_complete` event will *not* be triggered! .. versionadded:: 1.4.0 ''' self._widgets.pop(widget.uid, None) self._clock_uninstall() if not self._widgets: self._unregister()
[docs] def stop_property(self, widget, prop): '''Even if an animation is running, remove a property. It will not be animated futher. If it was the only/last property being animated, the animation will be stopped (see :attr:`stop`). ''' props = self._widgets.get(widget.uid, None) if not props: return props['properties'].pop(prop, None) # no more properties to animation ? kill the animation. if not props['properties']: self.stop(widget)
[docs] def cancel_property(self, widget, prop): '''Even if an animation is running, remove a property. It will not be animated further. If it was the only/last property being animated, the animation will be canceled (see :attr:`cancel`) .. versionadded:: 1.4.0 ''' props = self._widgets.get(widget.uid, None) if not props: return props['properties'].pop(prop, None) # no more properties to animation ? kill the animation. if not props['properties']: self.cancel(widget)
[docs] def have_properties_to_animate(self, widget): '''Return True if a widget still has properties to animate. .. versionadded:: 1.8.0 ''' props = self._widgets.get(widget.uid, None) if props and props['properties']: return True # # Private #
def _register(self): Animation._instances.add(self) def _unregister(self): if self in Animation._instances: Animation._instances.remove(self) def _initialize(self, widget): d = self._widgets[widget.uid] = { 'widget': widget, 'properties': {}, 'time': None} # get current values p = d['properties'] for key, value in self._animated_properties.items(): p[key] = (getattr(widget, key), value) # install clock self._clock_install() def _clock_install(self): if self._clock_installed: return Clock.schedule_interval(self._update, self._step) self._clock_installed = True def _clock_uninstall(self): if self._widgets or not self._clock_installed: return self._clock_installed = False Clock.unschedule(self._update) def _update(self, dt): widgets = self._widgets transition = self._transition calculate = self._calculate for uid in list(widgets.keys())[:]: anim = widgets[uid] widget = anim['widget'] if anim['time'] is None: anim['time'] = 0. else: anim['time'] += dt # calculate progression if self._duration: progress = min(1., anim['time'] / self._duration) else: progress = 1 t = transition(progress) # apply progression on widget for key, values in anim['properties'].items(): a, b = values value = calculate(a, b, t) setattr(widget, key, value) self.dispatch('on_progress', widget, progress) # time to stop ? if progress >= 1.: self.stop(widget) def _calculate(self, a, b, t): _calculate = self._calculate if isinstance(a, list) or isinstance(a, tuple): if isinstance(a, list): tp = list else: tp = tuple return tp([_calculate(a[x], b[x], t) for x in range(len(a))]) elif isinstance(a, dict): d = {} for x in iterkeys(a): if x not in b: # User requested to animate only part of the dict. # Copy the rest d[x] = a[x] else: d[x] = _calculate(a[x], b[x], t) return d else: return (a * (1. - t)) + (b * t) # # Default handlers # def on_start(self, widget): pass def on_progress(self, widget, progress): pass def on_complete(self, widget): pass def __add__(self, animation): return Sequence(self, animation) def __and__(self, animation): return Parallel(self, animation)
class Sequence(Animation): def __init__(self, anim1, anim2): super(Sequence, self).__init__() #: Repeat the sequence. See 'Repeating animation' in the header #: documentation. self.repeat = False self.anim1 = anim1 self.anim2 = anim2 self.anim1.bind(on_start=self.on_anim1_start, on_complete=self.on_anim1_complete, on_progress=self.on_anim1_progress) self.anim2.bind(on_complete=self.on_anim2_complete, on_progress=self.on_anim2_progress) @property def duration(self): return self.anim1.duration + self.anim2.duration def start(self, widget): self.stop(widget) self._widgets[widget.uid] = True self._register() self.anim1.start(widget) def stop(self, widget): self.anim1.stop(widget) self.anim2.stop(widget) props = self._widgets.pop(widget.uid, None) if props: self.dispatch('on_complete', widget) super(Sequence, self).cancel(widget) def stop_property(self, widget, prop): self.anim1.stop_property(widget, prop) self.anim2.stop_property(widget, prop) if (not self.anim1.have_properties_to_animate(widget) and not self.anim2.have_properties_to_animate(widget)): self.stop(widget) def cancel(self, widget): self.anim1.cancel(widget) self.anim2.cancel(widget) super(Sequence, self).cancel(widget) def on_anim1_start(self, instance, widget): self.dispatch('on_start', widget) def on_anim1_complete(self, instance, widget): self.anim2.start(widget) def on_anim1_progress(self, instance, widget, progress): self.dispatch('on_progress', widget, progress / 2.) def on_anim2_complete(self, instance, widget): '''Repeating logic used with boolean variable "repeat". .. versionadded:: 1.7.1 ''' if self.repeat: self.anim1.start(widget) else: self.dispatch('on_complete', widget) def on_anim2_progress(self, instance, widget, progress): self.dispatch('on_progress', widget, .5 + progress / 2.) class Parallel(Animation): def __init__(self, anim1, anim2): super(Parallel, self).__init__() self.anim1 = anim1 self.anim2 = anim2 self.anim1.bind(on_complete=self.on_anim_complete) self.anim2.bind(on_complete=self.on_anim_complete) @property def duration(self): return max(self.anim1.duration, self.anim2.duration) def start(self, widget): self.stop(widget) self.anim1.start(widget) self.anim2.start(widget) self._widgets[widget.uid] = {'complete': 0} self._register() self.dispatch('on_start', widget) def stop(self, widget): self.anim1.stop(widget) self.anim2.stop(widget) props = self._widgets.pop(widget.uid, None) if props: self.dispatch('on_complete', widget) super(Parallel, self).cancel(widget) def stop_property(self, widget, prop): self.anim1.stop_property(widget, prop) self.anim2.stop_property(widget, prop) if (not self.anim1.have_properties_to_animate(widget) and not self.anim2.have_properties_to_animate(widget)): self.stop(widget) def cancel(self, widget): self.anim1.cancel(widget) self.anim2.cancel(widget) super(Parallel, self).cancel(widget) def on_anim_complete(self, instance, widget): self._widgets[widget.uid]['complete'] += 1 if self._widgets[widget.uid]['complete'] == 2: self.stop(widget)
[docs]class AnimationTransition(object): '''Collection of animation functions to be used with the Animation object. Easing Functions ported to Kivy from the Clutter Project http://www.clutter-project.org/docs/clutter/stable/ClutterAlpha.html The `progress` parameter in each animation function is in the range 0-1. ''' @staticmethod
[docs] def linear(progress): '''.. image:: images/anim_linear.png''' return progress
@staticmethod
[docs] def in_quad(progress): '''.. image:: images/anim_in_quad.png ''' return progress * progress
@staticmethod
[docs] def out_quad(progress): '''.. image:: images/anim_out_quad.png ''' return -1.0 * progress * (progress - 2.0)
@staticmethod
[docs] def in_out_quad(progress): '''.. image:: images/anim_in_out_quad.png ''' p = progress * 2 if p < 1: return 0.5 * p * p p -= 1.0 return -0.5 * (p * (p - 2.0) - 1.0)
@staticmethod
[docs] def in_cubic(progress): '''.. image:: images/anim_in_cubic.png ''' return progress * progress * progress
@staticmethod
[docs] def out_cubic(progress): '''.. image:: images/anim_out_cubic.png ''' p = progress - 1.0 return p * p * p + 1.0
@staticmethod
[docs] def in_out_cubic(progress): '''.. image:: images/anim_in_out_cubic.png ''' p = progress * 2 if p < 1: return 0.5 * p * p * p p -= 2 return 0.5 * (p * p * p + 2.0)
@staticmethod
[docs] def in_quart(progress): '''.. image:: images/anim_in_quart.png ''' return progress * progress * progress * progress
@staticmethod
[docs] def out_quart(progress): '''.. image:: images/anim_out_quart.png ''' p = progress - 1.0 return -1.0 * (p * p * p * p - 1.0)
@staticmethod
[docs] def in_out_quart(progress): '''.. image:: images/anim_in_out_quart.png ''' p = progress * 2 if p < 1: return 0.5 * p * p * p * p p -= 2 return -0.5 * (p * p * p * p - 2.0)
@staticmethod
[docs] def in_quint(progress): '''.. image:: images/anim_in_quint.png ''' return progress * progress * progress * progress * progress
@staticmethod
[docs] def out_quint(progress): '''.. image:: images/anim_out_quint.png ''' p = progress - 1.0 return p * p * p * p * p + 1.0
@staticmethod
[docs] def in_out_quint(progress): '''.. image:: images/anim_in_out_quint.png ''' p = progress * 2 if p < 1: return 0.5 * p * p * p * p * p p -= 2.0 return 0.5 * (p * p * p * p * p + 2.0)
@staticmethod
[docs] def in_sine(progress): '''.. image:: images/anim_in_sine.png ''' return -1.0 * cos(progress * (pi / 2.0)) + 1.0
@staticmethod
[docs] def out_sine(progress): '''.. image:: images/anim_out_sine.png ''' return sin(progress * (pi / 2.0))
@staticmethod
[docs] def in_out_sine(progress): '''.. image:: images/anim_in_out_sine.png ''' return -0.5 * (cos(pi * progress) - 1.0)
@staticmethod
[docs] def in_expo(progress): '''.. image:: images/anim_in_expo.png ''' if progress == 0: return 0.0 return pow(2, 10 * (progress - 1.0))
@staticmethod
[docs] def out_expo(progress): '''.. image:: images/anim_out_expo.png ''' if progress == 1.0: return 1.0 return -pow(2, -10 * progress) + 1.0
@staticmethod
[docs] def in_out_expo(progress): '''.. image:: images/anim_in_out_expo.png ''' if progress == 0: return 0.0 if progress == 1.: return 1.0 p = progress * 2 if p < 1: return 0.5 * pow(2, 10 * (p - 1.0)) p -= 1.0 return 0.5 * (-pow(2, -10 * p) + 2.0)
@staticmethod
[docs] def in_circ(progress): '''.. image:: images/anim_in_circ.png ''' return -1.0 * (sqrt(1.0 - progress * progress) - 1.0)
@staticmethod
[docs] def out_circ(progress): '''.. image:: images/anim_out_circ.png ''' p = progress - 1.0 return sqrt(1.0 - p * p)
@staticmethod
[docs] def in_out_circ(progress): '''.. image:: images/anim_in_out_circ.png ''' p = progress * 2 if p < 1: return -0.5 * (sqrt(1.0 - p * p) - 1.0) p -= 2.0 return 0.5 * (sqrt(1.0 - p * p) + 1.0)
@staticmethod
[docs] def in_elastic(progress): '''.. image:: images/anim_in_elastic.png ''' p = .3 s = p / 4.0 q = progress if q == 1: return 1.0 q -= 1.0 return -(pow(2, 10 * q) * sin((q - s) * (2 * pi) / p))
@staticmethod
[docs] def out_elastic(progress): '''.. image:: images/anim_out_elastic.png ''' p = .3 s = p / 4.0 q = progress if q == 1: return 1.0 return pow(2, -10 * q) * sin((q - s) * (2 * pi) / p) + 1.0
@staticmethod
[docs] def in_out_elastic(progress): '''.. image:: images/anim_in_out_elastic.png ''' p = .3 * 1.5 s = p / 4.0 q = progress * 2 if q == 2: return 1.0 if q < 1: q -= 1.0 return -.5 * (pow(2, 10 * q) * sin((q - s) * (2.0 * pi) / p)) else: q -= 1.0 return pow(2, -10 * q) * sin((q - s) * (2.0 * pi) / p) * .5 + 1.0
@staticmethod
[docs] def in_back(progress): '''.. image:: images/anim_in_back.png ''' return progress * progress * ((1.70158 + 1.0) * progress - 1.70158)
@staticmethod
[docs] def out_back(progress): '''.. image:: images/anim_out_back.png ''' p = progress - 1.0 return p * p * ((1.70158 + 1) * p + 1.70158) + 1.0
@staticmethod
[docs] def in_out_back(progress): '''.. image:: images/anim_in_out_back.png ''' p = progress * 2. s = 1.70158 * 1.525 if p < 1: return 0.5 * (p * p * ((s + 1.0) * p - s)) p -= 2.0 return 0.5 * (p * p * ((s + 1.0) * p + s) + 2.0)
@staticmethod def _out_bounce_internal(t, d): p = t / d if p < (1.0 / 2.75): return 7.5625 * p * p elif p < (2.0 / 2.75): p -= (1.5 / 2.75) return 7.5625 * p * p + .75 elif p < (2.5 / 2.75): p -= (2.25 / 2.75) return 7.5625 * p * p + .9375 else: p -= (2.625 / 2.75) return 7.5625 * p * p + .984375 @staticmethod def _in_bounce_internal(t, d): return 1.0 - AnimationTransition._out_bounce_internal(d - t, d) @staticmethod
[docs] def in_bounce(progress): '''.. image:: images/anim_in_bounce.png ''' return AnimationTransition._in_bounce_internal(progress, 1.)
@staticmethod
[docs] def out_bounce(progress): '''.. image:: images/anim_out_bounce.png ''' return AnimationTransition._out_bounce_internal(progress, 1.)
@staticmethod
[docs] def in_out_bounce(progress): '''.. image:: images/anim_in_out_bounce.png ''' p = progress * 2. if p < 1.: return AnimationTransition._in_bounce_internal(p, 1.) * .5 return AnimationTransition._out_bounce_internal(p - 1., 1.) * .5 + .5