Kivy - aún más fácil, más nativo

Continuamos una serie de artículos sobre el desarrollo de aplicaciones móviles con el marco Kivy . Hoy hablaremos sobre la maravillosa biblioteca KivyMD , una biblioteca para crear una interfaz nativa al estilo de Material Design de Android, escrita en el marco de Kivy. Francamente, personalmente estoy infinitamente contento de no tener que esculpir y contemplar curvas, widgets personalizados oscuros y aterradores en las aplicaciones Kivy. Al usar la biblioteca KivyMD en nuestros proyectos y un poco de imaginación, es poco probable que alguien pueda distinguir visualmente si su programa está escrito en Java o si usa el marco Kivy y Python.
Descargue, desempaquete KivyMD, vaya al directorio raíz del archivo desempaquetado y realice la instalación:
python setup.py install
A continuación, instale las dependencias para KivyMD: 
pip install kivy-garden
garden install recycleview
Después de instalar la biblioteca, puede ejecutar una muestra de prueba desde el archivo que descargó y desempaquetó:
python kitchen_sink.py

Después del lanzamiento, verá una aplicación que muestra los widgets nativos y los controladores que están disponibles para su uso en sus proyectos:

En el artículo no nos detendremos en ningún widget de biblioteca específico (su creación y sus parámetros están bellamente descritos en la misma kitchen_sink.py), pero crearemos una aplicación de demostración simple, "Contactos", utilizando KivyMD. Nuestra aplicación podrá crear contactos y grupos, así como agregar contactos creados a ellos. Bueno, en el camino cubriremos con más detalle algunos aspectos de la creación de una interfaz de aplicación en Kivy:


Para una creación simple de un proyecto predeterminado en Kivy, recomiendo CreatorKivyProject , una descripción detallada del trabajo con el que se describe en este artículo . Entonces, siguiendo las instrucciones del artículo en el enlace, se creó el proyecto DemoKivyContacts. Abra el archivo a lo largo de la ruta DemoKivyContacts / libs / uix / kv / startscreen.kv , elimine implacablemente todo su contenido y "dibuje" la pantalla de inicio de su aplicación.

Así es como se ve el marcado de esta interfaz en Kivy-Language:



  






#:kivy 1.9.1 
#:import CreateContact libs.uix.createcontact.CreateContact 
#:import CallContact libs.uix.callcontact.CallContact 
#:import EmptyScreen libs.uix.emptyscreen.EmptyScreen 
#:import Toolbar kivymd.toolbar.Toolbar 
#:import MDTabbedPanel kivymd.tabs.MDTabbedPanel 
#:import MDTab kivymd.tabs.MDTab 

############################################################################### 
# 
#                              СТАРТОВЫЙ ЭКРАН 
# 
############################################################################### 
<StartScreen>: 
    id: root.manager 
    Screen: 
        name: 'root_screen' 
        BoxLayout: 
            #canvas: 
            #    Rectangle: 
            #        pos: self.pos 
            #        size: self.size 
            #        source: 'data/images/background.jpg' 
            orientation: 'vertical' 
            #################################################################### 
            # 
            #                            ACTION BAR 
            # 
            #################################################################### 
            Toolbar: 
                #canvas.before: 
                #    Rectangle: 
                #        pos: self.pos 
                #        size: self.size 
                #        source: 'data/images/background_toolbar.jpg' 

                id: action_bar 
                #background_color: app.data.alpha 
                background_color: app.theme_cls.primary_color 
                title: app.data.string_lang_contacts 
                left_action_items: [['menu', lambda x: app.nav_drawer.toggle()]] 
                right_action_items: [['more-vert', lambda x: None]] 
            #################################################################### 
            # 
            #                           TABBED PANEL 
            # 
            #################################################################### 
            MDTabbedPanel: 
                id: tabs 
                tab_display_mode: 'text' 
                #tab_color: app.data.alpha 
                tab_text_color: app.data.tab_text_color 
                tab_indicator_color: app.data.tab_indicator_color 

                MDTab: 
                    name: 'contacts' 
                    text: app.data.string_lang_contacts 
                    on_tab_press: app.on_tab_press(self.name) 
                    ScreenManager: 
                        id: screen_manager_tab_contacts 
                        Screen: 
                            name: 'empty_contacts_list' 
                            EmptyScreen: 
                                image: 'data/images/contacts.png' 
                                text: app.data.string_lang_add_contacts 
                                callback: app.show_form_create_contact 
                                disabled: False 
                        Screen: 
                            name: 'create_contact' 
                            CreateContact: 
                MDTab: 
                    name: 'groups' 
                    text: app.data.string_lang_groups 
                    on_tab_press: app.on_tab_press(self.name) 
                    ScreenManager: 
                        id: screen_manager_tab_groups 
                        Screen: 
                            name: 'empty_groups_list' 
                            EmptyScreen: 
                                image: 'data/images/contacts.png' 
                                text: app.data.string_lang_not_groups 
                                callback: lambda: app.create_group() 
                                disabled: False 
    Screen: 
        name: 'call_contact' 
        CallContact:



    Toolbar: 
        id: action_bar 
        background_color: app.theme_cls.primary_color 
        title: app.data.string_lang_contacts 
        left_action_items: [['menu', lambda x: app.nav_drawer.toggle()]] 
        right_action_items: [['more-vert', lambda x: None]] 


    MDTabbedPanel: 
        id: tabs 
        tab_display_mode: 'text' 
        tab_text_color: app.data.tab_text_color 
        tab_indicator_color: app.data.tab_indicator_color 

        MDTab: 
            name: 'contacts' 
            text: app.data.string_lang_contacts 
            on_tab_press: app.on_tab_press(self.name) 
            ScreenManager: 
                id: screen_manager_tab_contacts 
                Screen: 
                    name: 'empty_contacts_list' 
                    EmptyScreen: 
                        image: 'data/images/contacts.png' 
                        text: app.data.string_lang_add_contacts 
                        callback: app.show_form_create_contact 
                        disabled: False 
                Screen: 
                    name: 'create_contact' 
                    CreateContact: 
        MDTab: 
            name: 'groups' 
            text: app.data.string_lang_groups 
            on_tab_press: app.on_tab_press(self.name) 
            ScreenManager: 
                id: screen_manager_tab_groups 
                Screen: 
                    name: 'empty_groups_list' 
                    EmptyScreen: 
                        image: 'data/images/contacts.png' 
                        text: app.data.string_lang_not_groups 
                        callback: lambda: app.create_group

Importamos estos widgets de la biblioteca KivyMD al comienzo del archivo de marca startscreen.kv:

 
#:import Toolbar kivymd.toolbar.Toolbar 
#:import MDTabbedPanel kivymd.tabs.MDTabbedPanel 
#:import MDTab kivymd.tabs.MDTab

Estas instrucciones en Kivy-Language son similares a las importaciones en scripts de python:
from kivymd.toolbar import Toolbar 
from kivymd.tabs import MDTabbedPanel 
from kivymd.tabs import MDTab

Por cierto, puede incluir otros archivos de marcas en el archivo kv si la interfaz, por ejemplo, es demasiado compleja:
#:include your_kv_file.kv
Tenemos dos pestañas en MDTabbedPanel - "Contactos" y "Grupos". El primero ("Contactos") contendrá un widget de ScreenManager (administrador de pantalla) en el que colocaremos dos, en Java, Actividad:




        MDTab: 
            name: 'contacts' 
            text: app.data.string_lang_contacts 
            on_tab_press: app.on_tab_press(self.name) 
            ScreenManager: 
                id: screen_manager_tab_contacts 
                Screen: 
                    name: 'empty_contacts_list' 
                    EmptyScreen: 
                        image: 'data/images/contacts.png' 
                        text: app.data.string_lang_add_contacts 
                        callback: app.show_form_create_contact 
                        disabled: False 
                Screen: 
                    name: 'create_contact' 
                    CreateContact: 

Como puede ver, ScreenManager debe incluir uno o más widgets de pantalla (pantallas) que contendrán nuestro contenido (Actividad). En nuestro caso, estos son EmptyScreen (pantalla en blanco) y CreateContact (formulario para crear un nuevo contacto):
                Screen: 
                    name: 'empty_contacts_list' 

                    …

                Screen: 
                    name: 'create_contact'
... usando el objeto ScreenManager ...
            ScreenManager: 
                id: screen_manager_tab_contacts 
... en el código del programa por su identificador del marcado que creamos:
... y cambiando la Actividad pasando el nombre actual de la nueva pantalla al atributo actual :
self.manager_tab_contacts.current = 'create_contact'
Ahora "dibujemos" nuestra actividad: pantalla vacía (pantalla en blanco) y CreateContact (formulario para crear un nuevo contacto). Cree archivos de marcación de interfaz en el directorio del proyecto DemoKivyContacts / libs / uix / kv emptyscreen.kv y createcontact.kv y scripts de python del mismo nombre en el directorio DemoKivyContacts / libs / uix para administrar y transferir los parámetros a los widgets Creados en Pantalla vacía y CrearContacto:



#:kivy 1.9.1 
#:import MDLabel kivymd.label.MDLabel 
#:import MDFloatingActionButton kivymd.button.MDFloatingActionButton 

<EmptyScreen>: 
    id: empty_screen 

    Image: 
        source: root.image 
        pos_hint: {'center_x': .5, 'center_y': .6} 
        opacity: .5 

    MDLabel: 
        id: label 
        font_style: 'Headline' 
        theme_text_color: 'Primary' 
        color: app.data.text_color 
        text: root.text 
        halign: 'center' 

    MDFloatingActionButton: 
        id: float_act_btn 
        icon: 'plus' 
        size_hint: None, None 
        size: dp(56), dp(56) 
        opposite_colors: True
        elevation_normal: 8
        pos_hint: {'center_x': .9, 'center_y': .1} 
        background_color: app.data.floating_button_color 
        background_color_down: app.data.floating_button_down_color 
        disabled: root.disabled 
        on_release: root.callback()


#:kivy 1.9.1 
#:import SingleLineTextField kivymd.textfields.SingleLineTextField 
#:import MDIconButton kivymd.button.MDIconButton 
#:import MDFlatButton kivymd.button.MDFlatButton 
#:import MDFloatingActionButton kivymd.button.MDFloatingActionButton 

<CreateContact>: 
    orientation: 'vertical' 

    FloatLayout: 
        size_hint: 1, .3 

        Image: 
            id: avatar 
            pos_hint: {'center_y': .5} 
            source: 'data/images/avatar_empty.png' 

        MDFloatingActionButton: 
            icon: 'plus' 
            size_hint: None, None 
            size: dp(56), dp(56) 
            opposite_colors: True 
            elevation_normal: 8 
            pos_hint: {'center_x': .9, 'center_y': .20} 
            background_color: app.data.floating_button_color 
            background_color_down: app.data.floating_button_down_color 
            on_release: app.choice_avatar_contact() 

    BoxLayout: 
        orientation: 'vertical' 
        padding: 5, 5 
        size_hint: 1, .3 

        BoxLayout: 
            MDIconButton: 
                icon: 'account' 
                disabled: True 
            SingleLineTextField: 
                id: name_field 
                hint_text: 'ИФО' 

        BoxLayout: 
            MDIconButton: 
                icon: 'phone' 
                disabled: True 
            SingleLineTextField: 
                id: number_field 
                hint_text: 'Номер' 

        BoxLayout: 
            MDIconButton: 
                icon: 'email' 
                disabled: True 
            SingleLineTextField: 
                id: email_field 
                hint_text: 'E-mail' 

    Widget: 
        size_hint: 1, .3 

    AnchorLayout: 
        anchor_x: 'right' 
        anchor_y: 'bottom' 
        size_hint: 1, None 
        height: dp(40) 
        MDFlatButton: 
            id: button_ok 
            text: 'OK' 
            on_release: app.save_info_contact()


emptyscreen.py
from kivy.uix.floatlayout import FloatLayout 
from kivy.properties import StringProperty, ObjectProperty, BooleanProperty 

class EmptyScreen(FloatLayout): 
    image = StringProperty() 
    text = StringProperty() 
    callback = ObjectProperty() 
    disabled = BooleanProperty()
createcontact.py
from kivy.uix.boxlayout import BoxLayout 

class CreateContact(BoxLayout): 
    pass
En EmptyScreen, usamos otro widget de la biblioteca KivyMD - MDFloatingActionButton, que vale la pena describir. Esa misma mosca molesta que muchos usuarios quieren golpear:
    MDFloatingActionButton: 
        id: float_act_btn 
        icon: 'plus' 
        size_hint: None, None 
        size: dp(56), dp(56) 
        opposite_colors: True  # иконка белого/черного цветов 
        elevation_normal: 8  # длинна тени 
        pos_hint: {'center_x': .9, 'center_y': .1}  # самое нужное место на экране, которое кнопка обязательно закроет
        background_color: app.data.floating_button_color 
        background_color_down: app.data.floating_button_down_color 
        disabled: root.disabled 
        on_release: root.callback()
CreateContact utiliza widgets de la biblioteca KivyMD:
MDIconButton:
MDIconButton es un botón con un icono de vector. El conjunto completo de iconos oficiales de Google, vea el enlace . Todos ellos se usan y están disponibles en KivyMD.
SingleLineTextField:

MDFlatButton:

Llamará a la función de guardar la entrada del usuario:

        MDFlatButton:            on_release: app.save_info_contact()
Recibiremos la información ingresada por el usuario de los campos SingleLineTextField utilizando el método ya descrito anteriormente, por su identificación del atributo de texto :
DemoKivyContacts/libs/uix/kv/createcontact.kv
DemoKivyContacts/libs/programclass/showformcreatecontact.py

    def show_form_create_contact(self, *args): 
        '''Выводит на экран форму для создания нового контакта.''' 

        self.manager_tab_contacts.current = 'create_contact' 
        # <class 'libs.uix.createcontact.CreateContact'> 
        self._form_create_contact = \ 
            self.manager_tab_contacts.current_screen.children[0]

        ...

    def save_info_contact(self): 
        '''Сохраняет информацию о новом контакте.''' 

        name_contact = self._form_create_contact.ids.name_field.text 
        number_contact = self._form_create_contact.ids.number_field.text 
        mail_contact = self._form_create_contact.ids.email_field.text

        ...
Después de guardar los datos, el programa crea una lista de contactos si no se ha creado, o agrega uno nuevo a la lista ya existente y la muestra en la pantalla:



    def show_contacts(self, info_contacts): 
        ''' 
        :type info_contacts: dict; 
        :param info_contacts: { 
            'Name contact': ['Number contact\nMail contact', 'path/to/avatar'] 
        }; 

        ''' 

        if not self._contacts_items: 
            # Создаем список контактов. 
            self._contacts_list = ContactsList() 
            self._contacts_items = Lists( 
                dict_items=info_contacts, flag='three_list_custom_icon', 
                right_icons=self.data.right_icons, 
                events_callback=self._event_contact_item 
            ) 

            button_add_contact = Builder.template( 
                'ButtonAdd', disabled=False, 
                events_callback=self.show_form_create_contact 
            ) 
            self._contacts_list.add_widget(self._contacts_items) 
            self._contacts_list.add_widget(button_add_contact) 
            self.add_screens( 
                'contact_list', self.manager_tab_contacts, self._contacts_list 
            ) 
        else: 
            # Добавляет контакт к существующему списку
            # и выводит список на экран. 
            self._add_contact_item(info_contacts) 
            self.manager_tab_contacts.current = 'contact_list'
Presta atención a la función add_screens : agrega mediante programación una nueva Actividad y configúrala como la pantalla actual:
DemoKivyContacts/program.py

    def add_screens(self, name_screen, screen_manager, new_screen): 
        screen = Screen(name=name_screen)  # cоздаем новый экран
        screen.add_widget(new_screen)  #  добавляем Activity в созданный экран
        screen_manager.add_widget(screen)  #  добавляем экран в менеджер экранов
        screen_manager.current = name_screen  #  указываем менеджеру имя Activity, которое должно стать  текущим экраном приложения
Escribí un enlace pequeño (pero torpe) para crear listas MDList - DemoKivyContacts / libs / uix / lists.py
Puede crear fácilmente un elemento de lista con un icono a la izquierda y los iconos de vector a la derecha creando una instancia de la clase Listas con los parámetros necesarios.

    def show_contacts(self, info_contacts): 
        ''' 
        :type info_contacts: dict; 
        :param info_contacts: { 
            'Name contact': ['Number contact\nMail contact', 'path/to/avatar'] 
        }; 

        ''' 

        …

        self._contacts_items = Lists( 
            dict_items=info_contacts, flag='three_list_custom_icon', 
            right_icons=self.data.right_icons, 
            events_callback=self._event_contact_item 
        )
A continuación, la lista self._contacts_items arroja sobre cualquier widget deseado.
Al crear el elemento de la lista, pasamos la función _event_contact_item al parámetro events_callback para manejar los eventos de conexión :

    def _event_contact_item(self, *args): 
        '''События пункта списка контактов.''' 

        def end_call(): 
            self.screen.current = 'root_screen' 

        instanse_button = args[0] 
        if type(instanse_button) == RightButton: 
            name_contact, name_event = instanse_button.id.split(', ') 
            if name_event == 'call': 
                self.screen.current = 'call_contact' 
                data_contact = self.info_contacts[name_contact] 
                call_screen = self.screen.current_screen.children[0] 
                call_screen.name_contact = name_contact 
                call_screen.number_contact = data_contact[0].split('\n')[0] 
                call_screen.avatar = data_contact[1] 
                call_screen.callback = end_call 
            elif name_event == 'groups': 
                self._show_names_groups(name_contact) 
        else: 
            name_contact, name_event = args
Los identificadores de eventos 'call' y 'group' son los nombres de los iconos que especificamos en el parámetro right_icons :
DemoKivyContacts/libs/programdata.py
…
right_icons = ['data/images/call.png', 'data/images/groups.png']

Al hacer clic en el icono de llamada, se abrirá una pantalla de simulación de llamada saliente:
    def _event_contact_item(self, *args): 
        def end_call(): 
            self.screen.current = 'root_screen'if name_event == 'call': 
            self.screen.current = 'call_contact' 
            call_screen = self.screen.current_screen.children[0] 

            …

            call_screen.callback = end_call
Todos los widgets ya se han descrito, así que solo daré el diseño de esta Actividad:



#:kivy 1.9.1 
#:import MDIconButton kivymd.button.MDIconButton 
#:import MDFloatingActionButton kivymd.button.MDFloatingActionButton 
#:import MDLabel kivymd.label.MDLabel 

<CallContact>: 
    id: call_contact 

    Widget: 
        id: title_line 

        canvas: 
            Color: 
                rgba: app.theme_cls.primary_color 
            Rectangle: 
                size: self.size 
                pos: self.pos 

        size_hint_y: None 
        height: root.height * 30 // 100  # 30% от высоты экрана 
        pos: 0, call_contact.height - self.size[1] 

    Widget: 
        canvas: 
            Ellipse: 
                pos: self.pos 
                size: 150, 150 
                source: root.avatar if root.avatar else 'data/logo/kivy-icon-128.png' 
        pos: (call_contact.width // 2) - 75, call_contact.height * 61 // 100 

    BoxLayout: 
        orientation: 'vertical' 
        size_hint: 1, None 
        height: 50
        pos: self.pos[0], call_contact.height * 45 // 100 

        MDLabel: 
            id: name_contact 
            font_style: 'Headline' 
            theme_text_color: 'Primary' 
            color: app.data.text_color 
            text: root.name_contact if root.name_contact else 'Abonent' 
            halign: 'center' 
        MDLabel: 
            id: number_contact 
            font_style: 'Subhead' 
            theme_text_color: 'Primary' 
            color: app.data.text_color 
            text: root.number_contact if root.number_contact else '12345' 
            halign: 'center' 

    BoxLayout: 
        size_hint: None, None 
        height: 60 
        width: volume.width + dialpad.width + account.width + mic.width 
        pos: (call_contact.width // 2) - (self.width // 2), call_contact.height * 18 // 100 

        MDIconButton: 
            id: volume 
            icon: 'volume-mute' 
        MDIconButton: 
            id: dialpad 
            icon: 'dialpad' 
        MDIconButton: 
            id: account 
            icon: 'account' 
        MDIconButton: 
            id: mic 
            icon: 'mic' 

    MDFloatingActionButton: 
        id: phone_end 
        icon: 'phone-end' 
        size_hint: None, None 
        size: dp(56), dp(56) 
        opposite_colors: True  # иконка белого/черного цветов 
        elevation_normal: 8  # длинна тени 
        pos_hint: {'center_x': .5, 'center_y': .1} 
        background_color: app.data.floating_button_color_end_call 
        background_color_down: app.data.floating_button_down_color_end_call 
        on_release: root.callback()


El widget CallContact hereda de FloatLayout:
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import StringProperty, ObjectProperty

class CallContact(FloatLayout):
    callback = ObjectProperty(lambda: None)
    avatar = StringProperty(None)
    name_contact = StringProperty(None)
    number_contact = StringProperty(None)

Esto significa que todos los widgets y controladores se superpondrán entre sí, por lo que en el marcado utilicé una indicación porcentual de sus posiciones en relación con la altura de la pantalla principal:
pos: self.pos[0], call_contact.height * 45 // 100 

Ahora que sabe cómo funciona ScreenManager, echemos otro vistazo a la clase de gestión de la Actividad inicial:
from kivy.uix.screenmanager import ScreenManager
from kivy.properties import ObjectProperty

class StartScreen(ScreenManager):
    events_callback = ObjectProperty(lambda: None)
    '''Функция обработки сигналов экрана.'''
y marcado esqueleto:
<StartScreen>:
    Screen:
        name: 'root_screen'# Экран с вкладками — MDTabbedPanel

    Screen:
        name: 'call_contact'
        CallContact:
Es decir, cuando presiona el botón de llamada en el elemento de la lista de contactos, abrimos la Actividad para simular una llamada saliente y la cerramos cuando presiona el botón de finalizar llamada:
    def _event_contact_item(self, *args): 
        def end_call(): 
            self.screen.current = 'root_screen'if name_event == 'call': 
            self.screen.current = 'call_contact' 
            call_screen = self.screen.current_screen.children[0] 

            …

            call_screen.callback = end_call
No consideraremos el proceso de crear un grupo, ya que es similar al proceso de crear un nuevo contacto. Veamos el widget NavigationDrawer:
Para usar el panel NavigationDrawer, necesitamos crear su marcado y clase de control heredada de NavigationDrawer:




#:kivy 1.9.1

<NavDrawer>:
    NavigationDrawerIconButton:
        icon: 'settings'
        text: app.data.string_lang_settings
        on_release: app.events_program(self.text)
    NavigationDrawerIconButton:
        icon: 'view-module'
        text: app.data.string_lang_plugin
        on_release: app.events_program(self.text)
    NavigationDrawerIconButton:
        icon: 'info'
        text: app.data.string_lang_license
        on_release: app.events_program(self.text)
    NavigationDrawerIconButton:
        icon: 'collection-text'
        text: 'About'
        on_release: app.events_program(self.text)
    NavigationDrawerIconButton:
        icon: 'close-circle'
        text: app.data.string_lang_exit_key
        on_release: app.events_program(app.data.string_lang_exit_key)

DemoKivyContacts/program.py

from kivy.app import App
from kivy.properties import ObjectProperty

from kivymd.navigationdrawer import NavigationDrawer

class NavDrawer(NavigationDrawer):
    events_callback = ObjectProperty()

class Program(App):
    nav_drawer = ObjectProperty()

    def __init__(self, **kvargs):
        super(Program, self).__init__(**kvargs)

    def build(self):
        self.nav_drawer = NavDrawer(title=data.string_lang_menu)

Eso es todo por ahora. Puede ver el escenario completo del proyecto en github.
PS

¡Sin duda, la biblioteca KivyMD es una gran adición al marco Kivy! Espero que lo domines y lo apliques en tus proyectos.

Tengo una sugerencia para cambiar el formato de los artículos sobre el desarrollo de aplicaciones móviles usando Kivy: tome una aplicación nativa de Android preparada escrita en Java y cree una similar, pero escrita en Python usando el marco Kivy, cubriendo todo el proceso de desarrollo desde cero: cómo Los widgets y controladores se crean en Kivy, cómo usar clases dinámicas, qué es FloatLayout, etc.


Fuente: https://m.habr.com/en/post/313160/

Comentarios

Entradas populares de este blog

Ejemplos de interfaces gráficas realizadas con Python y PyQT5

PyQT5 – Python Tutorial