Продолжаем серию статей о разработке мобильных приложений с фреймворком Kivy. Сегодня речь пойдет о замечательной библиотеке KivyMD — библиотеке для построения нативного интерфейса в стиле Android Material Design, написанной с использованием и для фреймворка Kivy. Откровенно говоря, лично я бесконечно рад, что отпала необходимость лепить и созерцать кривые, темные и страшные кастомные виджеты в Kivy приложениях. Используя в своих проектах библиотеку KivyMD плюс немного фантазии, вряд ли кто-то сможет визуально отличить, написана ли ваша программа на Java или с использованием фрейворка Kivy и Python.


Скачайте, распакуйте KivyMD, зайдите в корневой каталог распакованного архива и выполните установку:


python setup.py install

Далее, установите зависимости для KivyMD:


pip install kivy-garden
garden install recycleview

После установки библиотеки вы можете запустить тестовый пример из скачанного вами и распакованного архива:


python kitchen_sink.py

После запуска вы увидите приложение, демонстрирующее нативные виджеты и контроллы, которые доступны вам для использования в ваших проектах:



В статье мы не будем останавливаться на каких-то конкретных виджетах библиотеки (их создание и параметры прекрасно описаны в том же самом kitchen_sink.py), а создадим простое демонстрационное приложение «Контакты» с использованием KivyMD. Наше приложение будет уметь создавать контакты и группы, а также добавлять в них созданные контакты. Ну, и попутно более детально осветим некоторые аспекты создания интерфейса приложений в Kivy:



Для простого создания дефолтного проекта на Kivy рекомендую CreatorKivyProject, детальное описание работы с которым описанно в этой статье. Итак, следуя инструкциям в статье по ссылке, проект DemoKivyContacts создан. Откроем файл по пути DemoKivyContacts/libs/uix/kv/startscreen.kv, безжалостно удалим все его содержимое и «нарисуем» стартовый экран своего приложения!



Вот так выглядит разметка данного интерфейса в Kivy-Language:


startscreen.kv
#: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
    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 с вкладками MDTab
    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


Эти виджеты библиотеки KivyMD мы импортировали в самом начале файла разметки startscreen.kv:


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

Данные инструкции в Kivy-Language аналогичны импорту в python сценариях:


from kivymd.toolbar import Toolbar 
from kivymd.tabs import MDTabbedPanel 
from kivymd.tabs import MDTab

К слову, в kv-файле вы можете включать другие файлы разметки, если интерфейс, например, слишком сложный:


#:include your_kv_file.kv

У нас имеются две вкладки на MDTabbedPanel — «Контакты» и «Группы». Первая («Контакты») будет содержать виджет ScreenManager (менеджер экранов), в котором мы разместим два, говоря языком Java, Activity:


Вкладка «Контакты»
        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: 

Как вы могли заметить, ScreenManager должен включать один или несколько виджетов Screen (экранов), которые будут содержать наш контент (Activity). В нашем случае это EmptyScreen (пустой экран) и CreateContact (форма создания нового контакта):



Переключаться между данными Activity мы будем по их именам:


                Screen: 
                    name: 'empty_contacts_list' 

                    …

                Screen: 
                    name: 'create_contact' 

                    …

… используя объект ScreenManager...


            ScreenManager: 
                id: screen_manager_tab_contacts 

… в программном коде по его идентификатору из созданной нами разметки:



… и переключая Activity посредством передачи аттрибуту current имени нового экрана:


self.manager_tab_contacts.current = 'create_contact'

Теперь «нарисуем» наши Activity — EmptyScreen (пустой экран) и CreateContact (форму создания нового контакта). Создадим файлы разметки интерфейса в директории проекта DemoKivyContacts/libs/uix/kv emptyscreen.kv и createcontact.kv и одноименные python сценарии в директории DemoKivyContacts/libs/uix для управления и передачи параметров созданным виджетам EmptyScreen и CreateContact:


emptyscreen.kv
#: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()

createcontact.kv
#: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

В EmptyScreen мы использовали еще один виджет из библиотеки KivyMD — MDFloatingActionButton, который стоит описать. Та самая назойливая муха, которую многим пользователям хочется прихлопнуть:



    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 используются виджеты библиотеки KivyMD:


MDIconButton:



MDIconButton — это кнопка с векторной иконкой. Полный набор официальных иконок от Google смотрите по ссылке. Все они используются и доступны в KivyMD.


SingleLineTextField:



MDFlatButton:



Будет вызывать функцию сохранения введенных пользователем данных:


        MDFlatButton: 
            …

            on_release: app.save_info_contact()

Получать введенную пользователем информацию из полей SingleLineTextField мы будем по уже описанному способу выше — по их id из аттрибута text:


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

        ...

После сохранения данных программа создает список контактов, если он не создан, или добавляет новый к уже существующему списку и выводит его на экран:



show_contacts
    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'

Обратите внимание на функцию add_screens — программное добавление нового Activity и установка его в качестве текущего экрана:


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, которое должно стать  текущим экраном приложения

Я написал небольшую (пока топорную) обвязку для создания списков MDList — DemoKivyContacts/libs/uix/lists.py


Создать пункт списка с иконкой слева и векторными иконками справа можно достаточно легко, создав экземпляр класса Lists c нужными параметрами.


    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 
        )


Далее список self._contacts_items кидаете на любой требуемый виджет.


При создании пункта списка мы передали параметру events_callback функцию _event_contact_item для обработки событий путнкта:


    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

Идентификаторы событий 'call' и 'group' — это имена иконок, которые мы указали в параметре right_icons:


DemoKivyContacts/libs/programdata.py


…

right_icons = ['data/images/call.png', 'data/images/groups.png']

При нажатии на иконку звонка откроется экран имитации исходящего вызова:


    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


Все виджеты в нем уже описаны, поэтому просто приведу макет разметки данного Activity:


show_contacts
#: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()

Виджет CallContact унаследован от 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)

Это значит, что все виджеты и контроллы в нем будут накладываться друг на друга, отчего в разметке я использовал процентное указание их позиций относительно высоты главного экрана:


pos: self.pos[0], call_contact.height * 45 // 100 

Теперь, когда вы знаете, как работает ScreenManager, давайте еще раз взглянем на управляющий класс стартового Activity:


from kivy.uix.screenmanager import ScreenManager
from kivy.properties import ObjectProperty

class StartScreen(ScreenManager):
    events_callback = ObjectProperty(lambda: None)
    '''Функция обработки сигналов экрана.'''

и скелет разметки:


<StartScreen>:
    Screen:
        name: 'root_screen'
        …
        # Экран с вкладками — MDTabbedPanel

    Screen:
        name: 'call_contact'
        CallContact:

То есть, при нажатии кнопки вызова в пункте списка контактов мы открываем Activity имитации исходящего вызова и закрываем его при нажатии кнопки «Отбой»:


    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

Процесс создания группы мы не будем рассматривать, так как он аналогичен процессу создания нового контакта. А остановимся на виджете NavigationDrawer:



Для использования панели NavigationDrawer мы должны создать ее разметку и управляющий класс, унаследованный от NavigationDrawer:


nawdrawer.kv
#: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)

На этом пока все. С полным сценарием проекта вы можете ознакомиться на github.


P.S

Без сомнения библиотека KivyMD является отличным дополнением к фреймворку Kivy! Надеюсь, вы ее освоите и будете применять в своих проектах.


У меня есть предложение поменять формат статей о разработке мобильных приложений с помощью Kivy: возьмем уже готовое нативное приложение для Android, написанное на Java и создадим аналогичное, но написанное на Python с использованием фреймворка Kivy, по ходу действия освещая весь процесс разработки с нуля: как создаются виджеты и контроллы в Kivy, как использовать динамические классы, что есть FloatLayout и т.д.


Будет ли вам интересен данный формат статей?

Проголосовало 134 человека. Воздержался 31 человек.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Поделиться с друзьями
-->

Комментарии (45)


  1. frol
    20.10.2016 12:56

    Спасибо за статью! А можно где-то готовую .apk получить? Я с интересом слежу за Kivy (и Python — это мой любимейший ЯП), но скорость загрузки приложения у него хромала последний раз когда я его пробовал, так что ваша фраза:


    Используя в своих проектах библиотеку KivyMD плюс немного фантазии, вряд ли кто-то сможет визуально отличить, написана ли ваша программа на Java или с использованием фрейворка Kivy и Python.

    вызывает у меня определённые сомнения в честности всего материала.


    1. HeaTTheatR
      20.10.2016 13:05

      Под "вряд ли кто-то сможет визуально отличить" я не имел в виду скорость загрузки приложения, которая в настоящий момент имеет интервал в 4 секунды. apk я не собирал за ненадобностью. Тем более, что сценарий демонстрационного приложения написан на Python 3, для которого сборку установочного пакета под Android я пока не тестировал.


      1. Sergey6661313
        22.10.2016 10:33

        уж простите не тогда это не разработка. Это всё равно что спроектировать красивое здание с красивым видом из окна, а потом вдруг выяснить что здесь копать нельзя, и вообще мы на марс не долетим. БЛин начинать надо всегда с «разведки боем». Т.е. создать минимально возможное но 100% рабочее приложение под среду в которой вы пишите.


        1. HeaTTheatR
          22.10.2016 11:15

          Уточните, что вы имеете в виду под "создать минимально возможное" рабочее приложение???


  1. velorias
    20.10.2016 13:00
    +2

    Спасибо за статью! Немного работаю с Киви, но даже не подозревал о подобной библиотеке. Продолжайте писать такие статьи! Особенно на тему Киви :-)


  1. Miay
    20.10.2016 14:28

    Круто! Спасибо за материал. А под Kivy+iOS есть какие-нибудь статейки?


    1. HeaTTheatR
      20.10.2016 14:30

      На просторах Интернета, наверное, есть. Поскольку не имею девайса под iOS, то не интересовался данной темой.


  1. vechnoe
    20.10.2016 14:46

    Насколько это юзабельно в продакшене?


  1. HeaTTheatR
    20.10.2016 14:56

    Каких-то критичных багов, которые роняют приложение я не наблюдал. Если руки у вас растут из нужного места, ваш проект будет юзабелен так же, как и обычное Java приложение. Но, некоторые находят минусы в большом (от 8 Mb) размере итогового установочного пакета для Android, первоначальную скорость запуска программы, написанной на Kivy… Хотя лично я считаю, что самый большой минус, помимо упомянутых, это невозможность восстановить из трея свернутое приложение. Вам придется программно запоминать (что, кстати, довольно легко) место последнего Activity, который пользователь посещал, чтобы при нажатии иконки в трее ваше приложение запускалось не заново, а с места, в котором оно было свернуто в трей.


    1. vechnoe
      20.10.2016 15:35

      Это хорошая новость, спасибо. 8 МБ не критично. Раньше считал, что Киви скорее мертв, чем жив. И смотрел на React Naitive, для реализации мобильного клиента для своего бэкенда (у меня вся кодовая база на Python, будет затратно ее переносить на JS).


      1. HeaTTheatR
        20.10.2016 15:58
        +1

        Раньше считал, что Киви скорее мертв, чем жив

        Kivy поддерживается и развивается разработчиками. KivyMD — тому еще одно доказательство.


  1. stPhoenix
    20.10.2016 15:48

    Отличная статья. Использую kivy+kivymd для создания программ под windows. Но есть вопрос который меня интересует: насколько много использует kivymd оперативную память? Для примера: пишу читалку книг формата fb2. Строки с книги помещаю в kivy.uix.label, а после в FloatLayout. В книге где 24 страницы памяти берет 160 мб. А когда попытался открыть книгу в 700 страниц — забрало 7.8 гб и так и не открыло…


    1. HeaTTheatR
      20.10.2016 15:57

      KivyMD — это библиотека написанная для Kivy и на Kivy. Поэтому потребляемая ею оперативная память зависит от самого Kivy.


      Строки с книги помещаю в kivy.uix.label, а после в FloatLayout.

      А зачем такой велосипед? Для вашего случая в Kivy существует замечательный виджет RstDocument!


      1. stPhoenix
        20.10.2016 19:33

        Спасибо за подсказку, стал читать о виджете. Впервые когда, его увидел, подумал, что подходить только к файлам с расширением .rst.


    1. veveve
      20.10.2016 16:35

      Каждый созданный виджет, естественно, будет память занимать. Вам единовременно нужно держать созданными только виджеты для видимой на экране области книги.


      1. stPhoenix
        20.10.2016 19:32

        Согласен с Вами о целесообразности такого подхода. Вчера, когда размышлял над ним, возник вопрос: а сколько будет занимать время подгрузка страниц? Плюс к этом, функцию подгрузки нужно вынести в отдельный поток с @mainthread, иначе программа будет зависать на момент подгрузки.
        Но, если не один я думаю, что это не плохо, стоит попробовать реализацию и после оценивать. Спасибо.


        1. HeaTTheatR
          20.10.2016 19:44

          В Kivy вместо потоков используйте объект Clock!


          1. stPhoenix
            20.10.2016 20:28

            Спасибо. Буду следовать данному правилу.


        1. veveve
          20.10.2016 19:56

          Я никогда не занимался созданием читалок, но вот пара мыслей, возможно, вам будет полезно.

          Если посмотреть, как реализованы другие читалки на Android, например, тот же Moon Reader, вы увидите, что пролистывание страниц там не непрерывно, а разделено главами книги: листаем страницы до конца главы, после чего новая глава загружается пару секунд. Возможно, это связано с проблемами, указанными выше.

          В таком случае алгоритм будет следующим. Загружаем одну главу из книги в память, создаем для неё соответствующие виджеты. Когда пользователь пролистывает страницы до конца главы (и готов читать следующую) удаляем все виджеты старой главы (память будет очищена сборщиком мусора) и, соответственно, загружаем новую главу, создавая виджеты для неё.

          Я не думаю, что даже в главном треде загрузка главы и создание для неё виджетов будет занимать много времени (если глава не на тысячу страниц, конечно). Можно попробовать.


          1. stPhoenix
            20.10.2016 20:33

            Спасибо за интересную идею. Как Вы подметили, нам не известно сколько в главе может быть страниц — от 1 и до N. Читалку делаю не для Android, а для планшета с Windows 10. Там есть читалка Bookviser Reader и я смотрю на ее производительность. Нет разбивки и подгрузки страниц. Когда я указываю прыжок на произвольную страницу (с 1 на 500) — она думает секунду. Я считаю, это ни сколько подгрузка — сколько перелистывание к нужному виджету. Потому еще рассматриваю вариант с RstDocument.


    1. HeaTTheatR
      20.10.2016 16:44

      Ну, и да, как вам правильно подсказывают, если вы пытаетесь загружать в память сразу 700 страниц, то это, по меньшей мере, не правильный подход.


  1. veveve
    20.10.2016 19:11

    Кстати, похоже, kivymd есть на PyPi:
    https://pypi.org/project/kivymd/

    pip install kivymd

    работает.


    1. Myrddin
      21.10.2016 01:12
      +1

      Версия намного старее, чем на гитлабе


  1. Carissalmo
    20.10.2016 19:24
    -1

    Еще одна проблема KivyMD — оно из коробки не умеет динамически создавать тени для виджетов, в итоге недоступны elevation and shadows по гайду MD.


    1. HeaTTheatR
      20.10.2016 19:33

      А это что?


      image


      А вот это?


      image


      Все работает и доступно. Из коробки!


    1. HeaTTheatR
      20.10.2016 19:42

      image


      Со значением elevation — 18


      1. Carissalmo
        20.10.2016 20:53
        +1

        На самом деле, не совсем. В kitchen_sink тени есть только у card и floating button, и фактически берутся из набора заранее отрисованных *.png файлов со стандартным значением dpi. Подход, мягко говоря, не универсальный. Опять же, отрисованы они не по гайдам…

        Ещё момент, в глаза сильно бросаются сгенерированные фреймворком круги. Из-за отсутствия нормального сглаживания приложение выглядит неряшливо. Со шрифтами тоже не всегда все хорошо…

        Andres Rodriguez(автор KivyMD) — молодец, и в одиночку пытается решить главную проблему фреймворка: все приложения, написанные на нем выглядят убого из-за отсутствия «из коробки» красиво оформленных виджетов. Разработчику приходится даже для простых вещей целиком отрисовывать интерфейс, либо довольствоваться тем, что есть. Автор и пишет, что текущая стадия проекта — early alpha, имеется множество детских болезней. Я бы не сказал, что тут 100% «продакшен реди» решение.


        1. HeaTTheatR
          20.10.2016 21:09

          Я бы не сказал, что тут 100% «продакшен реди»

          Возможно. Но это уже не тот серенький Kivy, каким был раньше. Есть десятки приложений на Java, которые смотрятся гораздо хуже.


        1. HeaTTheatR
          20.10.2016 21:13

          В kitchen_sink тени есть только у card и floating button

          Разве?


          image


  1. Myrddin
    21.10.2016 01:10

    Тоже недавно нашел KivyMD. Не похоже, что проект развивается, скорее автор реализовал нужный ему функционал и подзабил — слишком сыро пока.

    З.Ы. Что-то моему питону не нравится вш юникод )

    D:\dev\python\CreatorKivyProject>python main.py test test
    [INFO ] [Logger ] Record log in C:\Users\Merlin\.kivy\logs\kivy_16-10-21_4.txt
    [INFO ] [Kivy ] v1.9.1
    [INFO ] [Python ] v3.4.4 (v3.4.4:737efcadf5a6, Dec 20 2015, 19:28:18) [MSC v.1600 32 bit (Intel)]
    [INFO ] Creator Kivy Project version 0.1.1 ...

    [INFO ] \u0421\u043e\u0437\u0434\u0430\u043d\u0430 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430 test/test ...
    [INFO ] \u0421\u043e\u0437\u0434\u0430\u043d\u0430 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430 test/test/data/language ...
    [INFO ] \u0421\u043e\u0437\u0434\u0430\u043d\u0430 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430 test/test/data/settings ...
    [INFO ] \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0442\u043e\u0447\u043a\u0438 \u0432\u0445\u043e\u0434\u0430 main.py ...
    [ERROR ] [\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 - 'charmap' codec can't decode byte 0x81 in position 123] character maps to


    1. HeaTTheatR
      21.10.2016 09:02

      Да, есть такое дело… Пользователи Windows, по крайней мере, жаловались. Не знаю, в чем проблема, возможно в том, что я работал в редакторе под Linux… Не имею cистемы Windows в радиусе 10 км, поэтому исправить этот досадный баг не могу, уж извиняйте.


    1. HeaTTheatR
      21.10.2016 09:08
      +1

      … KivyMD. Не похоже, что проект развивается

      Проект развивается. Совсем недавно были добавлены новые виджеты. Я слежу за проектом.


  1. Myrddin
    21.10.2016 01:29
    +1

    Очень нравится python, но у всех модулей какие-то блокирующие регрессии без определенного срока решения:
    — kivy не поддерживает python 3.5
    — buldozer ios поддерживает только python 2.7
    — pyinstaller игнорирует манифесты
    — pyupdater работает только с onefile
    — kivymd в перманентной альфе

    Как-то очень туманно віглядит возможность нормального использования в продакшне — все время какие-то велосипеды надо изобретать и dirty-хаки делать. Благо в питоне это все сравнительно просто.


    1. HeaTTheatR
      21.10.2016 09:05

      kivy не поддерживает python 3.5

      Работа над этим уже ведется.


      buldozer ios поддерживает только python 2.7

      Не знаю, как там в iOS, но сборка установочных пакетов с Python 3.4 уже давно возможна.


      1. Sergey6661313
        22.10.2016 11:57

        просто напишите как.


        1. HeaTTheatR
          22.10.2016 12:27

          К концу серии статей о разработке с Kivy аналога продакшн проекта на Java — такая статья появится.


  1. cyber_lis
    21.10.2016 14:36

    HeaTTheatR, у меня в контактах 100 человек. Добавьте в ваше приложение в список контактов 100 человек и расскажите как быстро будет он прогружаться. Мгновенно ли появится весь список? Будет ли лаг при отрисовки 100 виджетов.

    Причем в списке 1 контакт у вас состоит из 3 лабелов и 2х кнопок и 1ой иконки. То есть 1 контакт из списка это 6 виджетов.
    100 контактов — 600 виджетов? Все ли верно?


    1. HeaTTheatR
      21.10.2016 14:41

      Да, я предполагал ваш вопрос. В демо приложении к статье список контактов выводится целиком и при размере контакт-листа в сто записей, да, это будет не производительно. Но ведь список контактов можно загружать по одному просто добавляя их в уже существующий MDList путем add_widget. Сейчас исправлю, протестирую, перезалью в репу и выложу гифку с демонстрацией вывода ста контактов.


    1. HeaTTheatR
      22.10.2016 09:40

      Оказалось неюзабельным выводить 100 контаков по одному: в этом случае, да, список появляется мгновенно, и растет на глазах, но!.. При этом жутко тормозит весь остальной интерфейс пока список не достигнет своего максимума. Посчитал "голое" время вывода 100 контактов (600 виджетов) — 6 секунд. Много! Было принято решение сделать окно прогресса от момента старта до компоновки всех 100 контактов:



    1. HeaTTheatR
      22.10.2016 09:49

      Прошу прощения за качество видео, мой мобильный Интернет провайдер не позволяет мне заливать ролики большего размера.


      1. cyber_lis
        22.10.2016 21:15

        Просто 100 контактов не так много. Если же кажется, что 100 контактов много, то это может быть приложение с заметками. А заметок точно может быть 100. А частенько список чего-либо состоит не из одного виджета, а, как в нашем случае, аж из 6ти каждый.
        И тут становится грустно. Приходится колдовать, искать обходные пути. Не каждому хватит энтузиазма.
        Я вам прошлый раз приводил пример с файловым менеджером. Там была та же история.
        1 файл — это лабел, иконка и контейнер. 30 файлов — 90 виджетов. Вот и ждешь пару секунд, пока прогрузится список из 30 файлов (и это официальный виджет от kivy)


        1. HeaTTheatR
          23.10.2016 09:05

          Я вам скажу больше, я на своем компьютере в файловом менеджере жду дольше :)


  1. nickD
    21.10.2016 14:42

    Kivy вещь классная но, скорость запуска приложения написанного на kivy,
    которая связана имхо с развертыванием интерпретатора,
    не позволит ему быть когда нибудь в продакшенах.


    1. HeaTTheatR
      21.10.2016 14:46
      +1

      Развертывание происходит единожды. Все последующие запуски приложения — это старт (мгновенный) сплэша, который пользователь будет созерцать порядка четырех секунд, до момента появления стартового Activity приложения.


    1. veveve
      21.10.2016 15:10

      kivy уже давно используют в продакшн, например:
      https://play.google.com/store/apps/details?id=org.kognitivo.kognitivo
      100 тыс. скачиваний, рейтинг 4,3

      Тут рассказывается про процесс создания и подводные камни:
      http://cheparev.com/kognitivo-challenge-your-brain/