← Volver al listado de tecnologías
Animaciones y Clock
Sistema Clock
El Clock de Kivy programa ejecución de funciones en el tiempo.
Funciones Principales
| Método | Descripció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ón | Efecto |
|---|---|
linear | Velocidad constante |
in_quad | Aceleración cuadrática |
out_quad | Desaceleración cuadrática |
in_out_quad | Acelera y desacelera |
in_cubic | Aceleración cúbica |
in_out_cubic | Acelera/desacelera cúbico |
in_elastic | Efecto elástico entrada |
out_elastic | Efecto elástico salida |
in_bounce | Rebote entrada |
out_bounce | Rebote 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
Clock.schedule_once()para ejecución diferidaClock.schedule_interval()para ejecución repetidaAnimationinterpola propiedades en el tiempo+encadena animaciones secuenciales&ejecuta animaciones en paralelotransitiondefine la curva de aceleración