← Volver al listado de tecnologías
Lenguaje KV
¿Qué es el Lenguaje KV?
KV es un lenguaje declarativo para diseñar interfaces en Kivy. Separa la lógica (Python) del diseño (KV).
Ventajas
- Código más limpio y mantenible
- Sintaxis concisa para UI
- Hot reload durante desarrollo
- Binding automático de propiedades
Sintaxis Básica
Estructura
# Comentario en KV
# Widget raíz (sin <>)
BoxLayout:
orientation: 'vertical'
Label:
text: 'Hola'
Button:
text: 'Presionar'
Reglas de Estilo (<>)
# Definir estilo para clase
<MiWidget>:
canvas.before:
Color:
rgba: 0.2, 0.2, 0.2, 1
Rectangle:
pos: self.pos
size: self.size
Carga de Archivos KV
Método 1: Automático por nombre
# main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
class MiWidget(BoxLayout):
pass
class MiApp(App): # Busca 'mi.kv'
def build(self):
return MiWidget()
# mi.kv
<MiWidget>:
Label:
text: 'Cargado automáticamente'
Método 2: Builder.load_file
from kivy.app import App
from kivy.lang import Builder
Builder.load_file('interfaz.kv')
class OtraApp(App):
def build(self):
return Builder.load_file('pantalla.kv')
Método 3: Builder.load_string
from kivy.app import App
from kivy.lang import Builder
KV = '''
BoxLayout:
Label:
text: 'Desde string'
'''
class StringApp(App):
def build(self):
return Builder.load_string(KV)
Propiedades en KV
Valores Estáticos
Label:
text: 'Texto fijo'
font_size: 24
color: 1, 0, 0, 1
Referencias con self
Widget:
canvas:
Rectangle:
pos: self.pos # Posición del widget
size: self.size # Tamaño del widget
Referencias con root
<MiLayout>:
Label:
text: root.titulo # Propiedad del widget raíz
Referencias con app
Label:
text: app.nombre # Propiedad de la clase App
Binding Automático
Las propiedades en KV se actualizan automáticamente:
<Contador>:
BoxLayout:
Label:
text: str(root.valor) # Se actualiza cuando cambia valor
Button:
text: '+'
on_press: root.incrementar()
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty
class Contador(BoxLayout):
valor = NumericProperty(0)
def incrementar(self):
self.valor += 1
Eventos en KV
on_press, on_release
Button:
text: 'Click'
on_press: print('Presionado')
on_release: print('Soltado')
on_text, on_focus
TextInput:
on_text: print('Texto:', self.text)
on_focus: print('Enfocado:', args[1])
Llamar métodos de root
<MiWidget>:
Button:
on_press: root.mi_metodo()
on_release: root.otro_metodo(self.text)
IDs y Referencias
Definir IDs
<Formulario>:
BoxLayout:
orientation: 'vertical'
TextInput:
id: campo_nombre
TextInput:
id: campo_email
Button:
text: 'Enviar'
on_press: root.enviar(campo_nombre.text, campo_email.text)
Acceder desde Python
class Formulario(BoxLayout):
def enviar(self, nombre, email):
print(f'Nombre: {nombre}, Email: {email}')
def limpiar(self):
self.ids.campo_nombre.text = ''
self.ids.campo_email.text = ''
Widgets Dinámicos
Agregar widgets desde KV
<ListaItems>:
id: lista
orientation: 'vertical'
class ListaItems(BoxLayout):
def agregar_item(self, texto):
self.add_widget(Label(text=texto))
Factory para crear widgets
from kivy.factory import Factory
# Registrar widget
Factory.register('MiBoton', cls=MiBoton)
Directivas KV
#:import
#:import rgba kivy.utils.get_color_from_hex
#:import Window kivy.core.window.Window
Label:
color: rgba('#FF5733')
text: str(Window.size)
#:set
#:set color_primario (0.2, 0.6, 0.8, 1)
#:set padding_base 20
BoxLayout:
padding: padding_base
Button:
background_color: color_primario
Canvas en KV
canvas.before y canvas.after
<WidgetConFondo>:
canvas.before:
Color:
rgba: 0.2, 0.2, 0.2, 1
Rectangle:
pos: self.pos
size: self.size
canvas.after:
Color:
rgba: 1, 0, 0, 0.5
Line:
rectangle: self.x, self.y, self.width, self.height
width: 2
Ejemplo Completo: Calculadora
# main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
class CalculadoraWidget(BoxLayout):
display = StringProperty('0')
def agregar_digito(self, digito):
if self.display == '0':
self.display = digito
else:
self.display += digito
def calcular(self):
try:
self.display = str(eval(self.display))
except:
self.display = 'Error'
def limpiar(self):
self.display = '0'
class CalculadoraApp(App):
def build(self):
return CalculadoraWidget()
if __name__ == '__main__':
CalculadoraApp().run()
# calculadora.kv
<CalculadoraWidget>:
orientation: 'vertical'
padding: 10
spacing: 5
Label:
text: root.display
font_size: '32sp'
halign: 'right'
size_hint_y: 0.2
GridLayout:
cols: 4
spacing: 5
Button:
text: '7'
on_press: root.agregar_digito('7')
Button:
text: '8'
on_press: root.agregar_digito('8')
Button:
text: '9'
on_press: root.agregar_digito('9')
Button:
text: '/'
on_press: root.agregar_digito('/')
Button:
text: 'C'
on_press: root.limpiar()
Button:
text: '='
on_press: root.calcular()
Testing con Archivos KV
# test_kv.py
import unittest
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
class TestKV(unittest.TestCase):
def test_cargar_kv_string(self):
kv = '''
BoxLayout:
Label:
id: mi_label
text: 'Test'
'''
widget = Builder.load_string(kv)
self.assertIsInstance(widget, BoxLayout)
self.assertEqual(widget.ids.mi_label.text, 'Test')
def test_ids_accesibles(self):
kv = '''
BoxLayout:
Button:
id: btn
text: 'Click'
'''
widget = Builder.load_string(kv)
self.assertIn('btn', widget.ids)
def test_binding_funciona(self):
kv = '''
BoxLayout:
value: 10
Label:
text: str(root.value)
'''
widget = Builder.load_string(kv)
self.assertEqual(widget.children[0].text, '10')
if __name__ == '__main__':
unittest.main()
Probar en Dispositivo con ADB
# Ver errores de parsing KV
adb logcat | grep -i "kv\|parser\|builder"
# Copiar archivo KV modificado al dispositivo (hot reload manual)
adb push main.kv /sdcard/kivy/miapp/main.kv
# Ver archivos de la app en el dispositivo
adb shell ls /data/data/com.ejemplo.miapp/files/app/
Resumen
- KV separa diseño de lógica
- Usa
<Widget>:para definir estilos self,root,appson referencias especialesid:permite acceder widgets desde Python conself.ids- El binding es automático en expresiones KV