← Volver al listado de tecnologías

ScreenManager y Navegación

Por: SiempreListo
kivyscreenmanagernavegaciónscreens

ScreenManager

Gestiona múltiples pantallas con transiciones.

Estructura Básica

from kivy.uix.screenmanager import ScreenManager, Screen

class PantallaInicio(Screen):
    pass

class PantallaConfig(Screen):
    pass

class MiApp(App):
    def build(self):
        sm = ScreenManager()
        sm.add_widget(PantallaInicio(name='inicio'))
        sm.add_widget(PantallaConfig(name='config'))
        return sm

Definir en KV

ScreenManager:
    PantallaInicio:
    PantallaConfig:

<PantallaInicio>:
    name: 'inicio'
    BoxLayout:
        orientation: 'vertical'

        Label:
            text: 'Pantalla de Inicio'

        Button:
            text: 'Ir a Configuración'
            on_press: root.manager.current = 'config'

<PantallaConfig>:
    name: 'config'
    BoxLayout:
        orientation: 'vertical'

        Label:
            text: 'Configuración'

        Button:
            text: 'Volver'
            on_press: root.manager.current = 'inicio'

Cambiar Pantalla

# Por nombre
screen_manager.current = 'nombre_pantalla'

# Desde un Screen
self.manager.current = 'otra_pantalla'

Dirección de Transición

# Dirección: 'left', 'right', 'up', 'down'
screen_manager.transition.direction = 'left'
screen_manager.current = 'siguiente'
Button:
    on_press:
        root.manager.transition.direction = 'left'
        root.manager.current = 'siguiente'

Tipos de Transición

TransiciónDescripción
SlideTransitionDeslizar (default)
FadeTransitionDesvanecimiento
SwapTransitionIntercambio con zoom
WipeTransitionBarrido
CardTransitionEfecto carta
NoTransitionSin animación
FallOutTransitionCaída
RiseInTransitionElevación
from kivy.uix.screenmanager import (
    ScreenManager, FadeTransition, SlideTransition
)

sm = ScreenManager(transition=FadeTransition())
# o cambiar después
sm.transition = SlideTransition(direction='up')
ScreenManager:
    transition: FadeTransition()

Personalizar Transiciones

from kivy.uix.screenmanager import SlideTransition

# Duración
sm.transition.duration = 0.5

# Dirección
sm.transition.direction = 'up'

Acceder a Pantallas

# Obtener pantalla por nombre
pantalla = sm.get_screen('inicio')

# Lista de nombres
nombres = sm.screen_names

# Pantalla actual
actual = sm.current_screen
nombre_actual = sm.current

# Verificar si existe
if sm.has_screen('config'):
    pass

Eventos de Screen

class MiPantalla(Screen):
    def on_pre_enter(self):
        """Antes de entrar (transición iniciando)"""
        print('Preparando entrada')

    def on_enter(self):
        """Después de entrar (transición completada)"""
        print('Pantalla visible')

    def on_pre_leave(self):
        """Antes de salir"""
        print('Preparando salida')

    def on_leave(self):
        """Después de salir"""
        print('Pantalla oculta')

Pasar Datos Entre Pantallas

Método 1: Propiedades en Screen

class DetalleScreen(Screen):
    item_id = NumericProperty(0)
    item_nombre = StringProperty('')

# Al navegar
detalle = sm.get_screen('detalle')
detalle.item_id = 42
detalle.item_nombre = 'Producto X'
sm.current = 'detalle'

Método 2: Via App

class MiApp(App):
    datos_compartidos = DictProperty({})

# Desde cualquier pantalla
App.get_running_app().datos_compartidos['clave'] = 'valor'

Método 3: Manager personalizado

class MiManager(ScreenManager):
    usuario_actual = ObjectProperty(None)

# Acceder desde Screen
self.manager.usuario_actual = usuario

Ejemplo: App con Navegación

# main.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import StringProperty

class LoginScreen(Screen):
    pass

class HomeScreen(Screen):
    usuario = StringProperty('')

class PerfilScreen(Screen):
    pass

class AppManager(ScreenManager):
    pass

class MiApp(App):
    def build(self):
        return AppManager()
# mi.kv
<AppManager>:
    LoginScreen:
    HomeScreen:
    PerfilScreen:

<LoginScreen>:
    name: 'login'
    BoxLayout:
        orientation: 'vertical'
        padding: 50
        spacing: 20

        Label:
            text: 'Iniciar Sesión'
            font_size: '24sp'

        TextInput:
            id: txt_usuario
            hint_text: 'Usuario'
            multiline: False

        TextInput:
            id: txt_password
            hint_text: 'Contraseña'
            password: True
            multiline: False

        Button:
            text: 'Entrar'
            on_press:
                app.root.get_screen('home').usuario = txt_usuario.text
                root.manager.current = 'home'

<HomeScreen>:
    name: 'home'
    BoxLayout:
        orientation: 'vertical'
        padding: 20

        Label:
            text: 'Bienvenido, ' + root.usuario
            font_size: '20sp'

        Button:
            text: 'Ver Perfil'
            on_press: root.manager.current = 'perfil'

        Button:
            text: 'Cerrar Sesión'
            on_press: root.manager.current = 'login'

<PerfilScreen>:
    name: 'perfil'
    BoxLayout:
        orientation: 'vertical'

        Label:
            text: 'Mi Perfil'

        Button:
            text: 'Volver'
            on_press: root.manager.current = 'home'
class NavManager(ScreenManager):
    historial = ListProperty([])

    def ir_a(self, nombre):
        if self.current != nombre:
            self.historial.append(self.current)
            self.transition.direction = 'left'
            self.current = nombre

    def volver(self):
        if self.historial:
            pantalla_anterior = self.historial.pop()
            self.transition.direction = 'right'
            self.current = pantalla_anterior
<MiScreen>:
    Button:
        text: 'Atrás'
        on_press: root.manager.volver()

Cargar Pantallas Dinámicamente

class MiApp(App):
    def build(self):
        self.sm = ScreenManager()
        self.sm.add_widget(MenuScreen(name='menu'))
        return self.sm

    def cargar_pantalla(self, nombre):
        if not self.sm.has_screen(nombre):
            if nombre == 'config':
                self.sm.add_widget(ConfigScreen(name='config'))
            elif nombre == 'perfil':
                self.sm.add_widget(PerfilScreen(name='perfil'))

        self.sm.current = nombre

Testing de Navegación

# test_screenmanager.py
import unittest
from kivy.uix.screenmanager import ScreenManager, Screen, SlideTransition

class TestScreenManager(unittest.TestCase):
    def test_agregar_screen(self):
        sm = ScreenManager()
        screen = Screen(name='home')
        sm.add_widget(screen)
        self.assertTrue(sm.has_screen('home'))

    def test_cambiar_screen(self):
        sm = ScreenManager()
        sm.add_widget(Screen(name='a'))
        sm.add_widget(Screen(name='b'))
        sm.current = 'b'
        self.assertEqual(sm.current, 'b')

    def test_transicion(self):
        sm = ScreenManager(transition=SlideTransition())
        self.assertIsInstance(sm.transition, SlideTransition)

    def test_screen_names(self):
        sm = ScreenManager()
        sm.add_widget(Screen(name='uno'))
        sm.add_widget(Screen(name='dos'))
        self.assertIn('uno', sm.screen_names)
        self.assertIn('dos', sm.screen_names)

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

Probar en Dispositivo con ADB

# Simular botón "atrás" de Android
adb shell input keyevent KEYCODE_BACK

# Navegar a home y volver a la app
adb shell input keyevent KEYCODE_HOME
adb shell am start -n com.ejemplo.miapp/org.kivy.android.PythonActivity

# Ver actividades en el stack
adb shell dumpsys activity activities | grep miapp

# Ver transiciones en logs
adb logcat | grep -i "screen\|transition"

Resumen