← Volver al listado de tecnologías

Propiedades y Eventos

Por: SiempreListo
kivypropiedadeseventosbinding

Sistema de Propiedades

Las propiedades de Kivy son observables: notifican cambios automáticamente.

Tipos de Propiedades

TipoUsoValor por defecto
StringPropertyTexto''
NumericPropertyNúmeros0
BooleanPropertyBooleanosFalse
ListPropertyListas[]
DictPropertyDiccionarios{}
ObjectPropertyObjetos/widgetsNone
OptionPropertyOpciones limitadasPrimer valor
BoundedNumericPropertyNúmeros con rango0
ColorPropertyColores 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

EventoCuándo se dispara
on_touch_downToque/click inicial
on_touch_moveMovimiento durante toque
on_touch_upFin 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