← Volver al listado de tecnologías
Propiedades y Eventos
Sistema de Propiedades
Las propiedades de Kivy son observables: notifican cambios automáticamente.
Tipos de Propiedades
| Tipo | Uso | Valor por defecto |
|---|---|---|
StringProperty | Texto | '' |
NumericProperty | Números | 0 |
BooleanProperty | Booleanos | False |
ListProperty | Listas | [] |
DictProperty | Diccionarios | {} |
ObjectProperty | Objetos/widgets | None |
OptionProperty | Opciones limitadas | Primer valor |
BoundedNumericProperty | Números con rango | 0 |
ColorProperty | Colores RGBA | [1,1,1,1] |
Definir Propiedades
from kivy.uix.widget import Widget
from kivy.properties import (
StringProperty, NumericProperty, BooleanProperty,
ListProperty, ObjectProperty, OptionProperty
)
class MiWidget(Widget):
# String
nombre = StringProperty('Sin nombre')
# Numérico
contador = NumericProperty(0)
# Booleano
activo = BooleanProperty(False)
# Lista
items = ListProperty([])
# Objeto
boton_ref = ObjectProperty(None)
# Opciones limitadas
estado = OptionProperty('idle', options=['idle', 'running', 'stopped'])
Observar Cambios
Método on_
class Contador(Widget):
valor = NumericProperty(0)
def on_valor(self, instance, value):
print(f'Valor cambió a: {value}')
if value >= 10:
print('¡Meta alcanzada!')
Usando bind()
class MiApp(App):
def build(self):
widget = MiWidget()
widget.bind(contador=self.cuando_cambia)
return widget
def cuando_cambia(self, instance, value):
print(f'Contador: {value}')
Binding en KV
<MiWidget>:
on_valor: print('Nuevo valor:', self.valor)
on_activo: root.toggle_estado()
Binding Bidireccional
<Formulario>:
BoxLayout:
TextInput:
id: entrada
text: root.texto
Label:
text: entrada.text # Se actualiza automáticamente
class Formulario(BoxLayout):
texto = StringProperty('Inicial')
def actualizar(self):
# Cambiar desde Python también actualiza el TextInput
self.texto = 'Nuevo valor'
Propiedades Avanzadas
AliasProperty
Propiedad calculada (getter/setter personalizado).
from kivy.properties import AliasProperty, NumericProperty
class Rectangulo(Widget):
ancho = NumericProperty(100)
alto = NumericProperty(50)
def get_area(self):
return self.ancho * self.alto
def set_area(self, value):
ratio = self.ancho / self.alto
self.alto = (value / ratio) ** 0.5
self.ancho = self.alto * ratio
area = AliasProperty(get_area, set_area, bind=['ancho', 'alto'])
BoundedNumericProperty
from kivy.properties import BoundedNumericProperty
class Slider(Widget):
valor = BoundedNumericProperty(50, min=0, max=100)
ReferenceListProperty
from kivy.properties import NumericProperty, ReferenceListProperty
class Punto(Widget):
x = NumericProperty(0)
y = NumericProperty(0)
pos = ReferenceListProperty(x, y)
# Ahora puedes hacer:
# punto.pos = (10, 20) -> actualiza x e y
Sistema de Eventos
Eventos de Widget
| Evento | Cuándo se dispara |
|---|---|
on_touch_down | Toque/click inicial |
on_touch_move | Movimiento durante toque |
on_touch_up | Fin del toque |
class MiWidget(Widget):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print(f'Tocado en: {touch.pos}')
return True # Consumir evento
return super().on_touch_down(touch)
def on_touch_move(self, touch):
if self.collide_point(*touch.pos):
print(f'Moviendo: {touch.pos}')
return super().on_touch_move(touch)
def on_touch_up(self, touch):
if self.collide_point(*touch.pos):
print('Soltado')
return super().on_touch_up(touch)
Eventos de Botón
from kivy.uix.button import Button
class MiBoton(Button):
def on_press(self):
print('Presionado')
def on_release(self):
print('Soltado')
def on_state(self, instance, value):
print(f'Estado: {value}') # 'normal' o 'down'
Eventos Personalizados
Registrar eventos
from kivy.uix.widget import Widget
class MiWidget(Widget):
def __init__(self, **kwargs):
self.register_event_type('on_custom')
super().__init__(**kwargs)
def on_custom(self, *args):
pass # Handler por defecto
def disparar_custom(self):
self.dispatch('on_custom', 'dato1', 'dato2')
Escuchar evento
widget = MiWidget()
widget.bind(on_custom=lambda inst, a, b: print(f'{a}, {b}'))
widget.disparar_custom()
Touch Events Avanzados
Información del touch
def on_touch_down(self, touch):
print(f'Posición: {touch.pos}')
print(f'Botón: {touch.button}') # 'left', 'right', 'middle'
print(f'Es doble click: {touch.is_double_tap}')
print(f'Tiempo: {touch.time_start}')
# Datos del usuario
touch.ud['mi_dato'] = 'valor'
return True
Grab (capturar touch)
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
touch.grab(self) # Capturar
return True
return super().on_touch_down(touch)
def on_touch_up(self, touch):
if touch.grab_current is self:
touch.ungrab(self) # Liberar
print('Touch completado')
return True
return super().on_touch_up(touch)
Clock Events
from kivy.clock import Clock
class MiWidget(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Una vez después de 2 segundos
Clock.schedule_once(self.mi_funcion, 2)
# Repetir cada segundo
self.evento = Clock.schedule_interval(self.actualizar, 1)
def mi_funcion(self, dt):
print(f'Ejecutado después de {dt} segundos')
def actualizar(self, dt):
print('Tick')
def detener(self):
self.evento.cancel()
Ejemplo: Drag and Drop
from kivy.uix.widget import Widget
class Draggable(Widget):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
touch.grab(self)
return True
return super().on_touch_down(touch)
def on_touch_move(self, touch):
if touch.grab_current is self:
self.center = touch.pos
return True
return super().on_touch_move(touch)
def on_touch_up(self, touch):
if touch.grab_current is self:
touch.ungrab(self)
return True
return super().on_touch_up(touch)
<Draggable>:
size: 100, 100
canvas:
Color:
rgba: 0.5, 0.8, 0.3, 1
Rectangle:
pos: self.pos
size: self.size
Testing de Propiedades y Eventos
# test_propiedades.py
import unittest
from kivy.properties import StringProperty, NumericProperty
from kivy.uix.widget import Widget
from kivy.clock import Clock
class MiWidget(Widget):
nombre = StringProperty('')
contador = NumericProperty(0)
class TestPropiedades(unittest.TestCase):
def test_string_property(self):
w = MiWidget()
w.nombre = 'Test'
self.assertEqual(w.nombre, 'Test')
def test_binding(self):
w = MiWidget()
valores = []
w.bind(contador=lambda inst, val: valores.append(val))
w.contador = 5
self.assertIn(5, valores)
def test_on_property_callback(self):
class TestWidget(Widget):
valor = NumericProperty(0)
cambios = []
def on_valor(self, instance, value):
self.cambios.append(value)
w = TestWidget()
w.valor = 10
self.assertIn(10, w.cambios)
if __name__ == '__main__':
unittest.main()
Probar en Dispositivo con ADB
# Simular toque en coordenadas específicas
adb shell input tap 500 800
# Simular swipe/drag
adb shell input swipe 100 500 400 500 300
# Simular multitouch (requiere Android 10+)
adb shell input motionevent DOWN 100 200
adb shell input motionevent MOVE 150 250
adb shell input motionevent UP 150 250
# Ver eventos de input en tiempo real
adb shell getevent -l
Resumen
- Las propiedades son observables con
on_<prop>obind() touch.grab()captura eventos para un widget específicoClock.schedule_interval()ejecuta código periódicamente- Los eventos personalizados se registran con
register_event_type() - El binding en KV es automático y reactivo