Продолжаем серию статей о разработке мобильных приложений с фреймворком 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:
#: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
Эти виджеты библиотеки 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:
#: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
В 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
...
После сохранения данных программа создает список контактов, если он не создан, или добавляет новый к уже существующему списку и выводит его на экран:
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:
#: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:
#: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 и т.д.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (45)
velorias
20.10.2016 13:00+2Спасибо за статью! Немного работаю с Киви, но даже не подозревал о подобной библиотеке. Продолжайте писать такие статьи! Особенно на тему Киви :-)
Miay
20.10.2016 14:28Круто! Спасибо за материал. А под Kivy+iOS есть какие-нибудь статейки?
HeaTTheatR
20.10.2016 14:30На просторах Интернета, наверное, есть. Поскольку не имею девайса под iOS, то не интересовался данной темой.
HeaTTheatR
20.10.2016 14:56Каких-то критичных багов, которые роняют приложение я не наблюдал. Если руки у вас растут из нужного места, ваш проект будет юзабелен так же, как и обычное Java приложение. Но, некоторые находят минусы в большом (от 8 Mb) размере итогового установочного пакета для Android, первоначальную скорость запуска программы, написанной на Kivy… Хотя лично я считаю, что самый большой минус, помимо упомянутых, это невозможность восстановить из трея свернутое приложение. Вам придется программно запоминать (что, кстати, довольно легко) место последнего Activity, который пользователь посещал, чтобы при нажатии иконки в трее ваше приложение запускалось не заново, а с места, в котором оно было свернуто в трей.
vechnoe
20.10.2016 15:35Это хорошая новость, спасибо. 8 МБ не критично. Раньше считал, что Киви скорее мертв, чем жив. И смотрел на React Naitive, для реализации мобильного клиента для своего бэкенда (у меня вся кодовая база на Python, будет затратно ее переносить на JS).
HeaTTheatR
20.10.2016 15:58+1Раньше считал, что Киви скорее мертв, чем жив
Kivy поддерживается и развивается разработчиками. KivyMD — тому еще одно доказательство.
stPhoenix
20.10.2016 15:48Отличная статья. Использую kivy+kivymd для создания программ под windows. Но есть вопрос который меня интересует: насколько много использует kivymd оперативную память? Для примера: пишу читалку книг формата fb2. Строки с книги помещаю в kivy.uix.label, а после в FloatLayout. В книге где 24 страницы памяти берет 160 мб. А когда попытался открыть книгу в 700 страниц — забрало 7.8 гб и так и не открыло…
HeaTTheatR
20.10.2016 15:57KivyMD — это библиотека написанная для Kivy и на Kivy. Поэтому потребляемая ею оперативная память зависит от самого Kivy.
Строки с книги помещаю в kivy.uix.label, а после в FloatLayout.
А зачем такой велосипед? Для вашего случая в Kivy существует замечательный виджет RstDocument!
stPhoenix
20.10.2016 19:33Спасибо за подсказку, стал читать о виджете. Впервые когда, его увидел, подумал, что подходить только к файлам с расширением .rst.
veveve
20.10.2016 16:35Каждый созданный виджет, естественно, будет память занимать. Вам единовременно нужно держать созданными только виджеты для видимой на экране области книги.
stPhoenix
20.10.2016 19:32Согласен с Вами о целесообразности такого подхода. Вчера, когда размышлял над ним, возник вопрос: а сколько будет занимать время подгрузка страниц? Плюс к этом, функцию подгрузки нужно вынести в отдельный поток с @mainthread, иначе программа будет зависать на момент подгрузки.
Но, если не один я думаю, что это не плохо, стоит попробовать реализацию и после оценивать. Спасибо.veveve
20.10.2016 19:56Я никогда не занимался созданием читалок, но вот пара мыслей, возможно, вам будет полезно.
Если посмотреть, как реализованы другие читалки на Android, например, тот же Moon Reader, вы увидите, что пролистывание страниц там не непрерывно, а разделено главами книги: листаем страницы до конца главы, после чего новая глава загружается пару секунд. Возможно, это связано с проблемами, указанными выше.
В таком случае алгоритм будет следующим. Загружаем одну главу из книги в память, создаем для неё соответствующие виджеты. Когда пользователь пролистывает страницы до конца главы (и готов читать следующую) удаляем все виджеты старой главы (память будет очищена сборщиком мусора) и, соответственно, загружаем новую главу, создавая виджеты для неё.
Я не думаю, что даже в главном треде загрузка главы и создание для неё виджетов будет занимать много времени (если глава не на тысячу страниц, конечно). Можно попробовать.stPhoenix
20.10.2016 20:33Спасибо за интересную идею. Как Вы подметили, нам не известно сколько в главе может быть страниц — от 1 и до N. Читалку делаю не для Android, а для планшета с Windows 10. Там есть читалка Bookviser Reader и я смотрю на ее производительность. Нет разбивки и подгрузки страниц. Когда я указываю прыжок на произвольную страницу (с 1 на 500) — она думает секунду. Я считаю, это ни сколько подгрузка — сколько перелистывание к нужному виджету. Потому еще рассматриваю вариант с RstDocument.
HeaTTheatR
20.10.2016 16:44Ну, и да, как вам правильно подсказывают, если вы пытаетесь загружать в память сразу 700 страниц, то это, по меньшей мере, не правильный подход.
veveve
20.10.2016 19:11Кстати, похоже, kivymd есть на PyPi:
https://pypi.org/project/kivymd/
pip install kivymd
работает.
Carissalmo
20.10.2016 19:24-1Еще одна проблема KivyMD — оно из коробки не умеет динамически создавать тени для виджетов, в итоге недоступны elevation and shadows по гайду MD.
HeaTTheatR
20.10.2016 19:42
Со значением elevation — 18
Carissalmo
20.10.2016 20:53+1На самом деле, не совсем. В kitchen_sink тени есть только у card и floating button, и фактически берутся из набора заранее отрисованных *.png файлов со стандартным значением dpi. Подход, мягко говоря, не универсальный. Опять же, отрисованы они не по гайдам…
Ещё момент, в глаза сильно бросаются сгенерированные фреймворком круги. Из-за отсутствия нормального сглаживания приложение выглядит неряшливо. Со шрифтами тоже не всегда все хорошо…
Andres Rodriguez(автор KivyMD) — молодец, и в одиночку пытается решить главную проблему фреймворка: все приложения, написанные на нем выглядят убого из-за отсутствия «из коробки» красиво оформленных виджетов. Разработчику приходится даже для простых вещей целиком отрисовывать интерфейс, либо довольствоваться тем, что есть. Автор и пишет, что текущая стадия проекта — early alpha, имеется множество детских болезней. Я бы не сказал, что тут 100% «продакшен реди» решение.HeaTTheatR
20.10.2016 21:09Я бы не сказал, что тут 100% «продакшен реди»
Возможно. Но это уже не тот серенький Kivy, каким был раньше. Есть десятки приложений на Java, которые смотрятся гораздо хуже.
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 toHeaTTheatR
21.10.2016 09:02Да, есть такое дело… Пользователи Windows, по крайней мере, жаловались. Не знаю, в чем проблема, возможно в том, что я работал в редакторе под Linux… Не имею cистемы Windows в радиусе 10 км, поэтому исправить этот досадный баг не могу, уж извиняйте.
HeaTTheatR
21.10.2016 09:08+1… KivyMD. Не похоже, что проект развивается
Проект развивается. Совсем недавно были добавлены новые виджеты. Я слежу за проектом.
Myrddin
21.10.2016 01:29+1Очень нравится python, но у всех модулей какие-то блокирующие регрессии без определенного срока решения:
— kivy не поддерживает python 3.5
— buldozer ios поддерживает только python 2.7
— pyinstaller игнорирует манифесты
— pyupdater работает только с onefile
— kivymd в перманентной альфе
Как-то очень туманно віглядит возможность нормального использования в продакшне — все время какие-то велосипеды надо изобретать и dirty-хаки делать. Благо в питоне это все сравнительно просто.HeaTTheatR
21.10.2016 09:05kivy не поддерживает python 3.5
Работа над этим уже ведется.
buldozer ios поддерживает только python 2.7
Не знаю, как там в iOS, но сборка установочных пакетов с Python 3.4 уже давно возможна.
Sergey6661313
22.10.2016 11:57просто напишите как.
HeaTTheatR
22.10.2016 12:27К концу серии статей о разработке с Kivy аналога продакшн проекта на Java — такая статья появится.
cyber_lis
21.10.2016 14:36HeaTTheatR, у меня в контактах 100 человек. Добавьте в ваше приложение в список контактов 100 человек и расскажите как быстро будет он прогружаться. Мгновенно ли появится весь список? Будет ли лаг при отрисовки 100 виджетов.
Причем в списке 1 контакт у вас состоит из 3 лабелов и 2х кнопок и 1ой иконки. То есть 1 контакт из списка это 6 виджетов.
100 контактов — 600 виджетов? Все ли верно?HeaTTheatR
21.10.2016 14:41Да, я предполагал ваш вопрос. В демо приложении к статье список контактов выводится целиком и при размере контакт-листа в сто записей, да, это будет не производительно. Но ведь список контактов можно загружать по одному просто добавляя их в уже существующий MDList путем add_widget. Сейчас исправлю, протестирую, перезалью в репу и выложу гифку с демонстрацией вывода ста контактов.
HeaTTheatR
22.10.2016 09:40Оказалось неюзабельным выводить 100 контаков по одному: в этом случае, да, список появляется мгновенно, и растет на глазах, но!.. При этом жутко тормозит весь остальной интерфейс пока список не достигнет своего максимума. Посчитал "голое" время вывода 100 контактов (600 виджетов) — 6 секунд. Много! Было принято решение сделать окно прогресса от момента старта до компоновки всех 100 контактов:
nickD
21.10.2016 14:42Kivy вещь классная но, скорость запуска приложения написанного на kivy,
которая связана имхо с развертыванием интерпретатора,
не позволит ему быть когда нибудь в продакшенах.HeaTTheatR
21.10.2016 14:46+1Развертывание происходит единожды. Все последующие запуски приложения — это старт (мгновенный) сплэша, который пользователь будет созерцать порядка четырех секунд, до момента появления стартового Activity приложения.
veveve
21.10.2016 15:10kivy уже давно используют в продакшн, например:
https://play.google.com/store/apps/details?id=org.kognitivo.kognitivo
100 тыс. скачиваний, рейтинг 4,3
Тут рассказывается про процесс создания и подводные камни:
http://cheparev.com/kognitivo-challenge-your-brain/
frol
Спасибо за статью! А можно где-то готовую .apk получить? Я с интересом слежу за Kivy (и Python — это мой любимейший ЯП), но скорость загрузки приложения у него хромала последний раз когда я его пробовал, так что ваша фраза:
вызывает у меня определённые сомнения в честности всего материала.
HeaTTheatR
Под "вряд ли кто-то сможет визуально отличить" я не имел в виду скорость загрузки приложения, которая в настоящий момент имеет интервал в 4 секунды. apk я не собирал за ненадобностью. Тем более, что сценарий демонстрационного приложения написан на Python 3, для которого сборку установочного пакета под Android я пока не тестировал.
Sergey6661313
уж простите не тогда это не разработка. Это всё равно что спроектировать красивое здание с красивым видом из окна, а потом вдруг выяснить что здесь копать нельзя, и вообще мы на марс не долетим. БЛин начинать надо всегда с «разведки боем». Т.е. создать минимально возможное но 100% рабочее приложение под среду в которой вы пишите.
HeaTTheatR
Уточните, что вы имеете в виду под "создать минимально возможное" рабочее приложение???