← Volver al listado de tecnologías

Animaciones y Clock

Por: SiempreListo
kivyanimacionesclocktiming

Sistema Clock

El Clock de Kivy programa ejecución de funciones en el tiempo.

Funciones Principales

MétodoDescripción
schedule_once(func, timeout)Ejecuta una vez después de timeout
schedule_interval(func, interval)Ejecuta repetidamente
unschedule(func)Cancela programación
create_trigger(func)Trigger reutilizable

Schedule Once

from kivy.clock import Clock

class MiWidget(Widget):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # Ejecutar después de 2 segundos
        Clock.schedule_once(self.mi_funcion, 2)

        # Ejecutar en el siguiente frame (timeout=0 o -1)
        Clock.schedule_once(self.inmediato, 0)

    def mi_funcion(self, dt):
        print(f'Ejecutado después de {dt:.2f}s')

    def inmediato(self, dt):
        print('Siguiente frame')

Schedule Interval

from kivy.clock import Clock

class Cronometro(Widget):
    tiempo = NumericProperty(0)

    def iniciar(self):
        self.evento = Clock.schedule_interval(self.tick, 1)

    def tick(self, dt):
        self.tiempo += 1
        print(f'Segundos: {self.tiempo}')

    def detener(self):
        self.evento.cancel()

    def reiniciar(self):
        self.detener()
        self.tiempo = 0

Triggers

Triggers evitan múltiples llamadas en el mismo frame.

from kivy.clock import Clock

class BuscadorWidget(Widget):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # Crear trigger que espera 0.5s después de última llamada
        self.buscar_trigger = Clock.create_trigger(self.buscar, 0.5)

    def on_texto_cambia(self, texto):
        # Múltiples cambios solo disparan una búsqueda
        self.buscar_trigger()

    def buscar(self, dt):
        print('Ejecutando búsqueda...')

Clase Animation

Animación Básica

from kivy.animation import Animation

# Mover widget a nueva posición en 1 segundo
anim = Animation(x=100, y=200, duration=1)
anim.start(widget)

Propiedades Animables

# Múltiples propiedades
anim = Animation(
    x=100,
    y=200,
    opacity=0.5,
    size=(200, 200),
    duration=1
)

Transiciones

TransiciónEfecto
linearVelocidad constante
in_quadAceleración cuadrática
out_quadDesaceleración cuadrática
in_out_quadAcelera y desacelera
in_cubicAceleración cúbica
in_out_cubicAcelera/desacelera cúbico
in_elasticEfecto elástico entrada
out_elasticEfecto elástico salida
in_bounceRebote entrada
out_bounceRebote salida
anim = Animation(x=300, transition='out_bounce', duration=1)
anim.start(widget)

Encadenar Animaciones

Secuencial (+)

# Primero mover X, luego mover Y
anim = Animation(x=200, duration=0.5) + Animation(y=200, duration=0.5)
anim.start(widget)

Paralelo (&)

# Mover y cambiar opacidad al mismo tiempo
anim = Animation(x=200, duration=1) & Animation(opacity=0, duration=1)
anim.start(widget)

Combinado

# Mover a X (0.5s), luego mover Y y fade simultáneamente (0.5s)
anim = (
    Animation(x=200, duration=0.5) +
    (Animation(y=200, duration=0.5) & Animation(opacity=0.5, duration=0.5))
)

Callbacks de Animación

from kivy.animation import Animation

def on_complete(anim, widget):
    print('Animación completada')

def on_progress(anim, widget, progress):
    print(f'Progreso: {progress:.0%}')

anim = Animation(x=300, duration=1)
anim.bind(on_complete=on_complete)
anim.bind(on_progress=on_progress)
anim.start(widget)

Repetir y Revertir

# Repetir infinitamente
anim = Animation(x=300, duration=1)
anim.repeat = True
anim.start(widget)

# Detener
anim.stop(widget)
# o cancelar todas
Animation.cancel_all(widget)
# Animación ida y vuelta
def animar_loop(widget):
    anim = Animation(x=300, duration=1) + Animation(x=0, duration=1)
    anim.repeat = True
    anim.start(widget)

Ejemplo: Botón Animado

from kivy.uix.button import Button
from kivy.animation import Animation

class BotonAnimado(Button):
    def on_press(self):
        # Encoger
        anim = Animation(size_hint=(0.9, 0.9), duration=0.1)
        anim.start(self)

    def on_release(self):
        # Volver a tamaño normal con rebote
        anim = Animation(size_hint=(1, 1), duration=0.3, t='out_elastic')
        anim.start(self)

Animaciones en KV

<MiWidget>:
    on_touch_down:
        Animation(opacity=0.5, duration=0.2).start(self)
    on_touch_up:
        Animation(opacity=1, duration=0.2).start(self)

Ejemplo: Loading Spinner

from kivy.uix.widget import Widget
from kivy.graphics import Ellipse, Rotate, PushMatrix, PopMatrix
from kivy.animation import Animation
from kivy.properties import NumericProperty

class Spinner(Widget):
    angulo = NumericProperty(0)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.dibujar()
        self.bind(angulo=self.actualizar_rotacion)

    def dibujar(self):
        with self.canvas:
            PushMatrix()
            self.rotacion = Rotate(angle=0, origin=self.center)
            Color(0.3, 0.6, 0.9, 1)
            self.arco = Ellipse(
                pos=self.pos,
                size=self.size,
                angle_start=0,
                angle_end=270
            )
            PopMatrix()

    def actualizar_rotacion(self, *args):
        self.rotacion.angle = self.angulo
        self.rotacion.origin = self.center

    def iniciar(self):
        anim = Animation(angulo=360, duration=1)
        anim.repeat = True
        anim.start(self)

    def detener(self):
        Animation.cancel_all(self)

Mainthread Decorator

Para código que debe ejecutarse en el hilo principal:

from kivy.clock import mainthread

class MiWidget(Widget):
    @mainthread
    def actualizar_ui(self, datos):
        # Seguro llamar desde otro hilo
        self.label.text = datos

Clock Avanzado

Frame Events

# Ejecutar cada frame (antes de dibujar)
Clock.schedule_interval(lambda dt: None, 0)

# FPS actual
print(Clock.get_fps())

# Tiempo desde inicio
print(Clock.get_time())

Testing de Animaciones

# test_animaciones.py
import unittest
from kivy.animation import Animation
from kivy.uix.widget import Widget
from kivy.clock import Clock

class TestAnimaciones(unittest.TestCase):
    def test_animation_propiedades(self):
        anim = Animation(x=100, duration=1)
        self.assertEqual(anim.duration, 1)

    def test_animation_secuencial(self):
        a1 = Animation(x=100, duration=0.5)
        a2 = Animation(y=100, duration=0.5)
        secuencia = a1 + a2
        self.assertIsInstance(secuencia, Animation)

    def test_animation_paralela(self):
        a1 = Animation(x=100, duration=1)
        a2 = Animation(y=100, duration=1)
        paralela = a1 & a2
        self.assertIsInstance(paralela, Animation)

    def test_clock_schedule(self):
        ejecutado = []
        def callback(dt):
            ejecutado.append(True)
        event = Clock.schedule_once(callback, 0)
        self.assertIsNotNone(event)

if __name__ == '__main__':
    unittest.main()

Probar en Dispositivo con ADB

# Ver FPS en tiempo real
adb shell dumpsys gfxinfo com.ejemplo.miapp | grep "Total frames"

# Detectar frames lentos (jank)
adb shell dumpsys gfxinfo com.ejemplo.miapp | grep "Janky"

# Grabar video de animación
adb shell screenrecord /sdcard/animacion.mp4 --time-limit 10
adb pull /sdcard/animacion.mp4

# Monitorear CPU durante animación
adb shell top -n 5 | grep miapp

Resumen