Приветствую всех! Как только дым от жаркой дискуссии в комментариях к моей статье Kivy — фреймворк для кроссплатформенной разработки №1 осел, и среди прочих пробился достойный внимания комментарий, мы (Mirimon, SeOd), подумали, что было бы интересно и нам и читателям самостоятельно сравнить Kivy, Xamarin.Forms и React Native, написав на них одно и тоже приложение, сопроводить его соответствующей статьей на Хабре, репой на GitHub и честно рассказать, кто с какими трудностями столкнулся при реализации. Собравшись в Телеграмме и обсудив детали, мы принялись за работу.

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

Три фреймворка — один эксперимент. Xamarin.Forms. Часть 2
Три фреймворка — один эксперимент. React Native. Часть 3

Данная статья является первой в цикле, поэтому начать хочется с ТЗ, который мы для себя набросали, чтобы результат получился похожий.

Вариант ТЗ:


  • Заметки должны быть структурированы по проектам
  • Заметки могут добавляться разными людьми, так что должен быть указан автор заметки
  • Заметки внутри проекта должны добавляться/удаляться/редактироваться
  • Заметки должны быть размером по контенту, но не более 150 пикселей
  • Удаление заметок должно быть как через контекстное меню самой заметки, так и через свайп

Примерный UI должен выглядеть как-то так:



Перед тем, как начать, небольшая справка по Kivy:
Kivy — кросcплатформенный графический фреймворк, написанный на языке программирования Python/Cython, основанный на OpenGL ES 2, направленный на создание современных пользовательских интерфейсов, больше ориентированный на работу с сенсорными устройствами. Приложения на Kivy работают на таких платформах как Linux, OS X, Windows, Android, iOS и Rapberry Pi. В разработке вам доступен широкий спектр библиотек Python начиная от Requests и заканчивая NumPy и OpenCV. Kivy имеет доступ практически ко всем нативным мобильным API (GPS, Camera, Accelerometer, Google API, если речь идет об Android), посредством PyJNIus (Android) и PyOBJus (iOS), которые автоматически оборачивают код на Java/Objective-C в интерфейс Python.

Kivy быстр. Это относится как к разработке приложений, так и к скорости выполнения приложений. Все критически важные функции реализованы на уровне Cи. Также Kivy использует GPU везде, где это имеет смысл. GPU выполяет бОльшую часть работы, тем самым значительно увеличивая производительность.

Kivy очень гибкок. Это означает, что быстро развивающаяся разработка Kivy позволяет мгновенно адаптироваться к новым технологиям. Разработчики Kivy не раз добавляли поддержку новых внешних устройств и программных протоколов, иногда даже до их выпуска. Kivy можно использовать в сочетании с большим количеством различных сторонних решений. Например, в Windows Kivy поддерживает WM_TOUCH, что означает, что любое устройство с драйверами Windows 7 Pen & Touch будет работать с Kivy. В OS X вы можете использовать устройства Apple с поддержкой Multi-Touch, такие как трекпады и мыши. В Linux вы можете использовать входные события ввода HID. В дополнение к этому Kivy поддерживает TUIO (Tangible User Interface Objects) и ряд других источников ввода.

Вы можете написать простое приложение с несколькими строками кода. Программы с Kivy создаются с использованием языка программирования Python, который является невероятно универсальным и мощным, но простым в использовании. Кроме того, разработчики Kivy создали собственный язык разметки графических интерфейсов, для создания сложных пользовательских GUI. Этот язык позволяет быстро настраивать, подключать и упорядочивать элементы приложения.

И, да, Kivy абсолютно бесплатен. Вы можете использовать его везде! В коммерческом продукте либо в Open Source.


Я приведу весь код приложения и покажу достаточно подробно, как реализуются те или иные элементы при разработке под мобильные платформы. В качестве IDE я всегда использую PyCharm, который отлично поддерживает синтаксис Kv Language — специальный DSL язык, на котором пишется UI представление вашего приложения. Скелет приложения создан с помощью консольной утилиты CreatorKivyProject, которая предоставляет базовые экраны с использованием шаблона MVVM.


В папке baseclass содержится логика виджетов и контроллов, реализованная на языке программирования Python, в kv — файлы описания интерфейса на языке Kv Language. Директория applibs используется для сторонних библиотек, в папке data находятся медиаконтент, базы данных и прочие данные. Файл main.py — это точка входа приложения. Ничем он не занимается, кроме как запуском рендера UI TodoList().run(), отловом ошибки в случае ее возникновения и выводом окна для отправки баг репорта, создан автоматически утилитой CreatorKivyProject, не имеет отношения к написанию нашего приложения, а потому не рассматривается.

Файл todolist.py с программным кодом реализует класс TodoList, который загружает макеты интерфейса, инициализирует их инстансы, следит за событиями хард клавиш устройства и возвращает наш первый экран, которые перечислены в Activity менеджере. После TodoList().run() вызывается функция build и возвращает виджет, который будет отображен на экране.

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


А вот схема нашего класса приложения:


todolist.py:
# -*- coding: utf-8 -*-

import os

from kivy.app import App
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.factory import Factory

from libs.applibs.kivymd.theming import ThemeManager
from libs.dataBase import DataBase


class TodoList(App, DataBase):
    title = 'Todo List'
    icon = 'icon.png'
    theme_cls = ThemeManager()
    theme_cls.primary_palette = 'BlueGrey'

    def __init__(self, **kvargs):
        super(TodoList, self).__init__(**kvargs)
        Window.bind(on_keyboard=self.eventsProgram)
        Window.softinput_mode = 'below_target'
        self.Window = Window
        self.pathToBase = '%s/data/dataProjects.json' % self.directory
        self.nameAuthor = u'Иванов Юрий'

    def build(self):
        self.setDataProjects()
        self.loadAllKvFiles(os.path.join(self.directory, 'libs', 'uix', 'kv'))
        self.rootScreen = Factory.RootScreen()  # стартовый экран программы
        # Инстансы Activity.
        self.activityManager = self.rootScreen.ids.activityManager
        self.listProjectsActivity = self.rootScreen.ids.listProjectsActivity
        self.listNotesActivity = self.rootScreen.ids.listNotesActivity
        self.addNewNoteActivity = self.rootScreen.ids.addNewNoteActivity

        return self.rootScreen

    def loadAllKvFiles(self, directory_kv_files):
        for kv_file in os.listdir(directory_kv_files):
            kv_file = os.path.join(directory_kv_files, kv_file)
            if os.path.isfile(kv_file):
                Builder.load_file(kv_file)

    def on_start(self):
        self.listProjectsActivity.setListProjects(self)

    def eventsProgram(self, instance, keyboard, keycode, text, modifiers):
        if keyboard in (1001, 27):
            if self.activityManager.current == 'add new note activity':
                self.activityManager.backActivity(
                    'list notes activity', self.addNewNoteActivity.ids.floatingButton)
            if self.activityManager.current == 'list notes activity':
                self.activityManager.current = 'list project activity'
        return True



Наше приложение состоит всего из трех Activity, переключением которых занимается менеджер экранов (ScreenMenager), который мы вернули в функции build:

#:import ListProjectsActivity libs.uix.baseclass.ListProjectsActivity.ListProjectsActivity
#:import ListNotesActivity libs.uix.baseclass.ListNotesActivity.ListNotesActivity
#:import AddNewNoteActivity libs.uix.baseclass.AddNewNoteActivity.AddNewNoteActivity
#:import ActivityManager libs.uix.baseclass.ActivityManager.ActivityManager

<RootScreen@BoxLayout>:
    orientation: 'vertical'
    spacing: dp(2)

    ActivityManager:
        id: activityManager
        ListProjectsActivity:
            id: listProjectsActivity
        ListNotesActivity:
            id: listNotesActivity
        AddNewNoteActivity:
            id: addNewNoteActivity

При старте приложения будет установлено то Activity, которое указано в ActivityManager первым. В нашем случае — это ListProjectsActivity. В приложении для списков проектов и задач я использовал ScrollView. Хотя правильнее было — RecycleView. Потому что первый, если постов и проектов будет за сотню, не справится. Точнее, будет очень долго рендерить списки. RecycleView позволяет вывести списки любой длины практически мгновенно. Но так как в любом случае при больших списках пришлось бы использовать либо динамическую подгрузку данных в список, либо разбиение на страницы, а в ТЗ это не обсуждалось, я использовал имеено ScrollView. Вторая причина состоит в том, что мне было лень переделывать списки под RecycleView (а он координально отличается в использовании от ScrollView), да и времени особо не было, потому что все приложение написано за четыре часа в перекурах и кофе брейках.

Стартовый экран со списком проектов (ListProjectsActivity.kv и ListProjectsActivity.py) выглядит следующим образом:


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

# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.screenmanager import Screen as Activity

from libs.uix.baseclass.InputDialog import InputDialog
from . ProjectItem import ProjectItem


class ListProjectsActivity(Activity):
    objApp = App.get_running_app()

    def setListProjects(self, objApp):
        for nameProject in objApp.dataProjects.keys():
            self.ids.layoutContainer.add_widget(ProjectItem(projectName=nameProject))

    def createNewProject(self, projectName):
        if projectName and not projectName.isspace():
            self.ids.layoutContainer.add_widget(ProjectItem(projectName=projectName))
            self.objApp.addProjectInBase(projectName)

    def deleteProject(self, instance):
        for projectName in self.objApp.dataProjects:
            if instance.projectName == projectName:
                self.objApp.deleteProjectFromBase(projectName)
                break

    def showDialogCreateProject(self, *args):
        InputDialog(
            title='Новый проект', hintText='Имя проекта',
            textButtonCancel='Отмена', textTuttonOk='Да',
            eventsCallback=self.createNewProject).show()

Вызов диологового окна:


В работе вызов окна и создание нового проекта будет выглядеть так:

На вопросе о данных приложения я не буду останавливаться, потому что данные представляют из себя обычный словарь вида

{"Name Project": [{"pathToAvatar": "", "nameDate": "", "nameAuthor": "", "textNote": ""}]}

и который хранится в директории data в виде простого json файла.

Посмотрим, что представляет из себя пункт с названием проекта и как в Kivy использовать удаление пункта из списка путем свайпа? Для этого мы должны наследовать поведение виджета в списке от класса SwipeBehavior библиотеки SwipeToDelete:

ProjectItemActivity.py
from kivy.properties import StringProperty
from kivy.uix.boxlayout import BoxLayout

from libs.applibs.swipetodelete import SwipeBehavior


class ProjectItemActivity(SwipeBehavior, BoxLayout):
    projectName = StringProperty()

    def on_touch_down(self, touch):
        if self.collide_point(touch.x, touch.y):
            self.move_to = self.x, self.y
            return super(ProjectItemActivity, self).on_touch_down(touch)

    def on_touch_move(self, touch):
        if self.collide_point(touch.x, touch.y):
            self.reduce_opacity()
            return super(ProjectItemActivity, self).on_touch_move(touch)

    def on_touch_up(self, touch):
        if self.collide_point(touch.x, touch.y):
            self.check_for_left()
            self.check_for_right()
            return super(ProjectItemActivity, self).on_touch_up(touch)

И описание пункта проекта в Kv разметке:

ProjectItemActivity.kv
<ProjectItemActivity>:
    swipe_rectangle: self.x, self.y , self.width, self.height
    swipe_timeout: 1000000
    swipe_distance: 1
    event_after_swipe: app.listActivity.deleteProject

    OneLineListItem:
        text: root.projectName
        on_press: app.listActivity.setNotesProject(root.projectName)

Вообще, у каждого виджета в Kivy есть метод on_touch с помощью которого вы можете отловить любые события, происходящие на экране. Вот небольшая часть списка доступных событий:

['double_tap_time', 'grab_state', 'is_double_tap', 'is_mouse_scrolling', 'is_touch', 'is_triple_tap', 'move', 'push', 'push_attrs', 'push_attrs_stack', 'scale_for_screen', 'time_end', 'time_start', 'time_update', 'triple_tap_time', 'ungrab', 'update_time_end']


Реализация контекстного меню для Android…

Здесь тоже не возникло никаких проблем, так это всего лишь стандартный виджет DropDown. Благодоря тому, что все виджеты и контроллы в Kivy вы можете кастомизировать настолько, насколько вам позволяет ваша фантазия, я с легкостью получил симпотичную менюшку. Слева базовый DropDown, справа — мой:

Разметка списка контекстного меню:

ContextMenuAndroidActivity.kv
#:import MDSeparator libs.applibs.kivymd.card.MDSeparator
#:import MenuItem libs.applibs.animdropdown.MenuItem

<ContextMenuAndroidActivity>:
    MenuItem:
        text: 'Редактировать'
        menu: root
        on_press: root.tapOnItem(self.text)

    MDSeparator:

    MenuItem:
        text: 'Удалить'
        menu: root
        on_press: root.tapOnItem(self.text)

Программная часть контекстного меню:

ContextMenuAndroidActivity.kv
from kivy.app import App
from kivy.clock import Clock

from libs.applibs.animdropdown import AnimMenuDropDown


class ContextMenuAndroidActivity(AnimMenuDropDown):
    def tapOnItem(self, textItem):
        objApp = App.get_running_app()
        if textItem == 'Удалить':
            objApp.listActivity.deletePost()
        else:
            objApp.activityManager.current = 'add new note activity'
            Clock.schedule_once(objApp.addNewNoteActivity.editNote, .5)

Далее мы импортируем кнопку MenuDropDown из библиотеки animdropdown, передаем ей в качестве параметра объект нашего контекстного меню и уже после добавляем эту кнопку в нужное нам месте нам экране. В нашем приложении это кнопка справа в карточке заметки:


Разметка Activity карточки заметки:


Базовый класс NoteActivity:
from kivy.app import App
from kivy.properties import StringProperty
from kivy.uix.boxlayout import BoxLayout

from libs.applibs.animdropdown import MenuButton
from libs.applibs.swipetodelete import SwipeBehavior
from . ContextMenu import ContextMenu


class NoteActivity(SwipeBehavior, BoxLayout):
    nameDate = StringProperty()
    textNote = StringProperty()
    pathToAvatar = StringProperty()

    def __init__(self, **kwargs):
        super(NoteActivity, self).__init__(**kwargs)
        self.objApp = App.get_running_app()
        menuButton = MenuButton(
            dropdown_cls=ContextMenu, icon='dots-vertical', _on_dropdown_fnc=self.setCurrentPost)
        self.ids.titleBox.add_widget(menuButton)

    def setCurrentPost(self, *args):
        self.objApp.listNotesActivity.checkCurentPost = self


Программная реализация ListNotesActivity:
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.screenmanager import Screen as Activity
from kivy.properties import ObjectProperty

from . NoteActivity import NoteActivity


class ListNotesActivity(Activity):
    checkCurentPost = ObjectProperty()
    objApp = App.get_running_app()

    def clearList(self):
        if self.objApp.activityManager.current == 'list project activity':
            self.ids.layoutContainer.clear_widgets()

    def addNewNote(self, objApp):
        objApp.activityManager.current = 'add new note activity'

    def setDefaultcheckCurentPost(self):
        self.checkCurentPost = lambda x: None

    def setNotesProject(self, nameProject):
        self.ids.toolBar.title = nameProject
        for dataProject in self.objApp.dataProjects[nameProject][1]:
            self.ids.layoutContainer.add_widget(NoteActivity(
                textNote=dataProject['textNote'],
                nameDate=dataProject['nameDate'],
                pathToAvatar=dataProject['pathToAvatar']))

    def deletePost(self, instance=None):
        # Удаление свайпом.
        if not self.checkCurentPost:
            checkCurentPost = instance
        else:
            checkCurentPost = self.checkCurentPost
            self.ids.layoutContainer.remove_widget(self.checkCurentPost)

        nameProject = self.ids.toolBar.title
        self.objApp.deleteNoteFromBase(nameProject, checkCurentPost.textNote)

    def checkScroll(self):
        if self.checkCurentPost and type(self.checkCurentPost) is not NoteActivity:
            self.checkCurentPost(self)

Как управлять Activity приложения? Для того чтобы переключится с одного Activity на другое, мы должны указать менеджеру экранов имя нового Activity:

class ListNotesActivity(Activity):
    ...

    def addNewNote(self, *args):
        self.objApp.activityManager.current = 'add new note activity'

… где 'add new note activity' имя Activity для добавления новой заметки.

Экран и разметка Activity AddNewNoteActivity:


Базовый класс:

from kivy.app import App
from kivy.animation import Animation
from kivy.uix.screenmanager import Screen as Activity
from kivy.metrics import dp

from libs.uix.baseclass.NoteActivity import NoteActivity


class AddNewNoteActivity(Activity):
    objApp = None
    edit = False
    oldTextNote = ''

    def animationButton(self):
        self.objApp = App.get_running_app()
        self.ids.toolBar.title = self.objApp.listNotesActivity.ids.toolBar.title
        Animation(size=(dp(56), dp(56)), d=.5, t='in_out_cubic').start(self.ids.floatingButton)

    def addNewNotes(self, textNote):
        if self.edit:
            nameProject = self.ids.toolBar.title
            self.objApp.addEditNoteInBase(nameProject, textNote, self.oldTextNote)
            self.objApp.activityManager.backActivity('list notes activity', self.ids.floatingButton)
            self.objApp.listNotesActivity.checkCurentPost.textNote = textNote
            self.edit = False
            return

        self.objApp.listNotesActivity.ids.layoutContainer.add_widget(
            NoteActivity(
                textNote=textNote, nameDate='%s\n%s' % (
                self.objApp.nameAuthor, self.objApp.getDate()),
                pathToAvatar='data/images/avatar.png'))
        self.objApp.addNoteInBase(self.ids.toolBar.title, textNote, 'data/images/avatar.png')

    def editNote(self, interval):
        self.edit = True
        self.ids.textInput.text = self.objApp.listNotesActivity.checkCurentPost.textNote
        self.oldTextNote = self.ids.textInput.text

Для анимации кнопки я использовал событие on_enter, которое вызывается в момент установки Activity на экран:

В разметке:

<AddNewNoteActivity>
    on_enter: root.animationButton()

В Python коде:

class AddNewNoteActivity(Activity):
    def animationButton(self):
        Animation(size=(dp(56), dp(56)), d=.5, t='in_out_cubic').start(self.ids.floatingButton)


В отличие от Xamarin.Forms UI в Kivy будет выглядеть везде одинаково. Так что, если вы пишите приложение для двух платформ (Android и iOS), вы должны учитывать это при разметке интерфейса и указании свойств виджетам. Или же делать две разметки для двух платформ (логика остается неизменной). Это плюс, так как рендер UI и события не зависят от особенностей платформы, вы не используете нативные API для управления этими действиями, что позволяет вашему приложению безболезнено выполнятся практически на любой платформе. Вся графика рендерится с помощью нативных вызовов OpenGL и SDL2 на GPU, что позволяет очень быстро рисовать менюшки, кнопки и прочие прелести графического интерфейса включая 2D и 3D графику.

Данное приложение использует Android UI MaterialDesign. Например, последний мой проект имел адаптивный интерфес:



А вот демонстрация возможностей в стиле Material Design:



Как я уже говорил, Kivy не использует нативные API для рендера UI, поэтому позволяет эмулировать различные модели устройств и платформ с помощью модуля screen. Достаточно запустить ваш проект с нужными параметрами, чтобы на компьютере открылось окно тестируемого приложения так, как если бы оно было запущено на реальном устройстве. Звучит странно, но поскольку Kivy абстрагируется от платформы в отрисовке UI, это позволяет не использовать тяжелые и медленные эмуляторы для тестов. Это касается только UI. Например, тестовое приложение, описываемое в этой статье тестировалось с параметрами -m screen:droid2, portrait, scale=.75 что один в один соответствует моему реальному устройству.

Полный список параметров модуля screen:
devices = {
    # device: (name, width, height, dpi, density)
    'onex': ('HTC One X', 1280, 720, 312, 2),
    'one': ('HTC One', 1920, 1080, 468, 3),
    'onesv': ('HTC One SV', 800, 480, 216, 1.5),
    's3': ('Galaxy SIII', 1280, 720, 306, 2),
    'note2': ('Galaxy Note II', 1280, 720, 267, 2),
    'droid2': ('Motorola Droid 2', 854, 480, 240, 1.5),
    'xoom': ('Motorola Xoom', 1280, 800, 149, 1),
    'ipad': ('iPad (1 and 2)', 1024, 768, 132, 1),
    'ipad3': ('iPad 3', 2048, 1536, 264, 2),
    'iphone4': ('iPhone 4', 960, 640, 326, 2),
    'iphone5': ('iPhone 5', 1136, 640, 326, 2),
    'xperiae': ('Xperia E', 480, 320, 166, 1),
    'nexus4': ('Nexus 4', 1280, 768, 320, 2),
    'nexus7': ('Nexus 7 (2012 version)', 1280, 800, 216, 1.325),
    'nexus7.2': ('Nexus 7 (2013 version)', 1920, 1200, 323, 2),

    # taken from design.google.com/devices
    # please consider using another data instead of
    # a dict for autocompletion to work
    # these are all in landscape
    'phone_android_one': ('Android One', 854, 480, 218, 1.5),
    'phone_htc_one_m8': ('HTC One M8', 1920, 1080, 432, 3.0),
    'phone_htc_one_m9': ('HTC One M9', 1920, 1080, 432, 3.0),
    'phone_iphone': ('iPhone', 480, 320, 168, 1.0),
    'phone_iphone_4': ('iPhone 4', 960, 640, 320, 2.0),
    'phone_iphone_5': ('iPhone 5', 1136, 640, 320, 2.0),
    'phone_iphone_6': ('iPhone 6', 1334, 750, 326, 2.0),
    'phone_iphone_6_plus': ('iPhone 6 Plus', 1920, 1080, 400, 3.0),
    'phone_lg_g2': ('LG G2', 1920, 1080, 432, 3.0),
    'phone_lg_g3': ('LG G3', 2560, 1440, 533, 3.0),
    'phone_moto_g': ('Moto G', 1280, 720, 327, 2.0),
    'phone_moto_x': ('Moto X', 1280, 720, 313, 2.0),
    'phone_moto_x_2nd_gen': ('Moto X 2nd Gen', 1920, 1080, 432, 3.0),
    'phone_nexus_4': ('Nexus 4', 1280, 768, 240, 2.0),
    'phone_nexus_5': ('Nexus 5', 1920, 1080, 450, 3.0),
    'phone_nexus_5x': ('Nexus 5X', 1920, 1080, 432, 2.6),
    'phone_nexus_6': ('Nexus 6', 2560, 1440, 496, 3.5),
    'phone_nexus_6p': ('Nexus 6P', 2560, 1440, 514, 3.5),
    'phone_samsung_galaxy_note_4': ('Samsung Galaxy Note 4',
                                    2560, 1440, 514, 3.0),
    'phone_samsung_galaxy_s5': ('Samsung Galaxy S5', 1920, 1080, 372, 3.0),
    'phone_samsung_galaxy_s6': ('Samsung Galaxy S6', 2560, 1440, 576, 4.0),
    'phone_sony_xperia_c4': ('Sony Xperia C4', 1920, 1080, 400, 2.0),
    'phone_sony_xperia_z_ultra': ('Sony Xperia Z Ultra', 1920, 1080, 348, 2.0),
    'phone_sony_xperia_z1_compact': ('Sony Xperia Z1 Compact',
                                     1280, 720, 342, 2.0),
    'phone_sony_xperia_z2z3': ('Sony Xperia Z2/Z3', 1920, 1080, 432, 3.0),
    'phone_sony_xperia_z3_compact': ('Sony Xperia Z3 Compact',
                                     1280, 720, 313, 2.0),
    'tablet_dell_venue_8': ('Dell Venue 8', 2560, 1600, 355, 2.0),
    'tablet_ipad': ('iPad', 1024, 768, 132, 1.0),
    'tablet_ipad_mini': ('iPad Mini', 1024, 768, 163, 1.0),
    'tablet_ipad_mini_retina': ('iPad Mini Retina', 2048, 1536, 326, 2.0),
    'tablet_ipad_pro': ('iPad Pro', 2732, 2048, 265, 2.0),
    'tablet_ipad_retina': ('iPad Retina', 2048, 1536, 264, 2.0),
    'tablet_nexus_10': ('Nexus 10', 2560, 1600, 297, 2.0),
    'tablet_nexus_7_12': ('Nexus 7 12', 1280, 800, 216, 1.3),
    'tablet_nexus_7_13': ('Nexus 7 13', 1920, 1200, 324, 2.0),
    'tablet_nexus_9': ('Nexus 9', 2048, 1536, 288, 2.0),
    'tablet_samsung_galaxy_tab_10': ('Samsung Galaxy Tab 10',
                                     1280, 800, 148, 1.0),
    'tablet_sony_xperia_z3_tablet': ('Sony Xperia Z3 Tablet',
                                     1920, 1200, 282, 2.0),
    'tablet_sony_xperia_z4_tablet': ('Sony Xperia Z4 Tablet',
                                     2560, 1600, 297, 2.0)TodoList()
        app.run()

}


Что можно сказать в заключение? Хорош ли Kivy? Бесспорно хорош! Если вы владеете замечательным языком программирования Python, вы без труда сможете делать приложения под мобильные (и не только) платформы с не менее замечательным фреймворком Kivy.

Плюсы разработки приложений с использованием фреймворка Kivy:

  • Поскольку мы имеем дело с Python, скорость разработки приложений в разы превышает скорость разработки на любом другом языке программирования или фреймворке.
  • Мегатонны уже готовых библиотек Python, которые вы можете использовать в своих проектах: OpenCV, Django, Flask, NumPy, ffmpeg, sqlite3, lxml и тысячи других.
  • Поскольку Kivy для отрисовки графики использует OpenGL и GPU, а также собственные виджеты и контроллы, скорость рендера UI очень высока и вы полностью избавлены от головной боли, что присутствует в других фреймворках, которым нужно для реализации некоторых частей интерфейса лезть в нативную часть.
  • Вы используете натив только там, где нужен доступ к специфическим функциям платформы, которых просто не может быть в действительно кроссплатформенном фреймворке: например, доступ к геолокации, доступ к камере, технологии BlueTooth…

    Реализация доступа к нативному API Android для получения IMEI и модели устройства с помощью PyJnius:

def _get_model_android():
    from jnius import autoclass

    Build = autoclass('android.os.Build')
    return str(Build.DEVICE)

def _get_imei_android():
    from jnius import autoclass

    Service = autoclass('org.renpy.android.PythonActivity').mActivity
    Context = autoclass('android.content.Context')
    TelephonyManager = Service.getSystemService(Context.TELEPHONY_SERVICE)
    return str(TelephonyManager.getDeviceId())


Для примера — реализация нативного получения IMEI устройства на Java:

import android.content.Context;
import android.telephony.TelephonyManager;

public class GetImeiAndroid {
    public String getImeiAndroid()
    {
        TelephonyManager  tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); 
        String IMEINumber = tm.getDeviceId(); 
        return IMEINumber;
    }
}

  • Вы можете использовать сторонние jar библиотеки в своих проектах, если речь идет об Android.
  • Вы полностью владеете всеми событиями происходящими на экране: тач, мультитач, свайп, продавливание и др. событиями без ухода в натив так это является неотъемлемой частью Kivy.

Возможности Kivy в Touch устройствах:





Несмотря на все плюсы, Kivy имеет и ряд недостатков:

  • Скорость «холодного старта», то есть, первый запуск приложение от момента, когда все библиотеки будут развернуты, довольно долгий. Последующие — обычные, но дольше, чем натив, зависит от нагрузки процессора мобильного устройства.
  • Работа со списками. Можно за пол секунды вывести список размером в 100 000 пунктов (например, карточки пользователей, витрина, цитаты), но с одним условием — все карточки должны быть одинаковой высоты. Если выводить список, например, цитат, с заранее неизвестным количеством текста, но целиком, то за один раз больше десяти пунктов нельзя показывать, так как это займет около 10-15 секунд. В этом случае придется подгружать по 10-15 элементов при прокрутке списка.
  • Нельзя вывести текст размер которого превышает 6500 символов (3.5 страницы печатного текста) — получим черный экран. Это решается разбиением текста с последующим его склеиванием, что все равно кажется костыльным. Однако не понятно, кому может прийти в голову выводить такое количество текста за раз. Особенно если речь идет о мобильных платформах.

> Больше статей о Kivy

Виртуальная машина (первое сообщение от ZenCODE) от разработчиков Kivy готовая и настроеная для сборки проектов под обе ветки Python.

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


  1. Lietto
    27.08.2018 12:20

    На волне хайпа могли бы и Flutter захватить


    1. HeaTTheatR Автор
      27.08.2018 12:32

      Ну, кто хотел, тот присоединился. Хотя мы не офишировали данное предприятие.


  1. Neikist
    27.08.2018 12:26

    После того как разочаровался в мобильной 1с ради интереса стал посматривать в сторону нативной и кроссплатформенной разработки под мобилки.
    Посматривал на киви, ибо питон нравится своей выразительностью, но все же фреймворк мне чем то не нравился. Язык разметки, слабый набор стандартных виджетов, скорость (особенно старта, да и в целом), начал больше смотреть в сторону нативной разработки.
    А вот недавно наткнулся на довольно интересную вещь: flutter+dart. Пока только изучаю, но выглядит довольно вкусно и на мой взгляд перспективно.
    Язык довольно приятен и выразителен, компилируемый в натив, типы, интересная система создания виджетов, много стандартных виджетов выглядящих нативно, по заявлениям авторов лишен некоторых проблем, что в теории позволит выиграть по скорости анимаций и подобного у разработки на java под андроид, вроде как будет основным фреймворком под фуксию, и в последнее время заметны телодвижения гугла по продвижению.
    Пока единственный минус что вижу — малое коммьюнити.


    1. HeaTTheatR Автор
      27.08.2018 12:36

      Язык разметки, слабый набор стандартных виджетов
      Скажите, в чём сильные стороны других языков разметки? Помимо того, что Kv Language решает все существующие задачи в плане UI, помимо этого данных язык разметки всё ещё является Python и поддерживает конструкции этого языка прямо в разметке. В чём слабость? Каких виджетов вам не хватило?


      1. Neikist
        27.08.2018 12:37

        Мне в принципе не нравится наличие языков разметки)


        1. HeaTTheatR Автор
          27.08.2018 12:46

          То есть, слабость языка разметки в том, что он является языком разметки?


          1. Neikist
            27.08.2018 12:50

            Язык разметки, слабый набор стандартных виджетов

            Про слабость я не говорил, да и неправильно будет, не изучал. Я просто его наличие в минусы записал. А слабость это про стандартные виджеты. Флаттер подкупил наличием нативно выглядящих виджетов из коробки. И работой с виджетами в целом. Довольно интересная система. Хотя конечно это все вопрос вкуса.


            1. HeaTTheatR Автор
              27.08.2018 17:02

              А слабость это про стандартные виджеты.

              Kivy потому и кроссплатформенный на 99%, что у него нет привязки к конкретной ОС. Разработчики даже отказывались от некоторых решений, например, с работой аудио, только потому, что предлагаемое решение было заявлено под одну конкретную платформу. Это касается и библиотеки KivyMD, которая предоставляет набор виджетов в стиле Material. Да, она хороша, но это не кроссплатформенное решение. Поэтому она не включена в стандартную комплектацию Kivy. Kivy — работает практически везде. Поэтому никакие Material-ы или Flat дизайном не для него. Хотите Material — пишите приложение в этом стиле. Хотите Flat — верстайте в стиле Flat. И то и другое — пожалуйста. Но из коробки есть только страшная серая тема. А дальше вы делаете всё, на что хватит вашей фантазии.


    1. saag
      27.08.2018 13:05

      А вот недавно наткнулся на довольно интересную вещь: flutter+dart. Пока только изучаю, но выглядит довольно вкусно и на мой взгляд перспективно

      Это вы на второе пришествие Христа Fuchsi'yu намекаете?:-) Меня вот тоже занимает этот вопрос.


      1. Neikist
        27.08.2018 13:21

        В части перспективности да, ходят в сети всякие слухи и подозрения. А вот вкусно и без этого все выглядит) По крайней мере точно хотя бы один-два проектика для себя сделать попробую, и дарт понравился как язык, и архитектура заложенная во фреймворк.


  1. valis
    27.08.2018 13:15

    Спасибо за обзор. Мы сейчас тоже находимся на стадии выбора инструмента кроссплатформеной разработки. Пока остановились на Xamarin (т.к у нас в основном .net разработчики) но и периодически поглядываем в сторону других инструментов.
    Вот первое что бы хотелось слышать это примеры успешных крупных проектов, в которых применили данный инструмент, а уж потом сравнение кода.


    1. HeaTTheatR Автор
      27.08.2018 13:35

      В конце статьи под спойлером есть
      некоторые ссылки на видео проектов, разработаных с помощью Kivy. Их довольно много. В прошлой статье также есть ссылки на видео и приложения. Можете ознакомиться.


  1. aknew
    27.08.2018 13:37

    Для сборки приложения под iOS нужна macOS.

    А разве этим не практически любая кроссплатформа страдает? Не думаю, что Kivy тут исключение, во всяком случае, такая проблема была с тем что доводилось пощупать (а довелось пощупать Qt, React, PhoneGap и Intel MOE, последние два реально только пощупать)


    1. HeaTTheatR Автор
      27.08.2018 13:59

      Не знаю, не щупал. Завтра должна выйти статья Kivy. Xamarin. React Native. Xamarin. Часть вторая. Думаю, там автор расскажет об этом.


    1. Mirimon
      27.08.2018 14:23

      *спойлер
      Xamarin позволяет iOS под виндой собирать.


      1. aknew
        27.08.2018 14:45

        А вот это действительно интересно и, возможно, будет для кого-то серьезным плюсом. Не знал. Подождем статьи )


      1. ZAMnoTEX
        28.08.2018 12:49

        Подробнее, пожалуйста. А то приходится работать вот с этой не очень удобной штукой. И до сих пор считал, что по-другому не получится.


        1. HeaTTheatR Автор
          28.08.2018 13:00

          Об этом лучше спрашивать у автора второй части статьи


          1. ZAMnoTEX
            29.08.2018 10:41

            Как подтвердили в комментариях второй части статьи, сборка приложения под iOS невозможна без MacOS и XCode. Это часть политики Apple.


      1. SHLAKBAUM
        28.08.2018 14:23

        Но всё равно для этого нужен mac в качестве build агента.


  1. kreo_OL
    27.08.2018 19:57

    Так а другие два приложения где?


    1. kreo_OL
      27.08.2018 20:09

      А, увидел коммент о том что ксамарин завтра выйдет


    1. dleshko
      28.08.2018 00:20

      Присоединяюсь, ещё очень интересно какой именно комментарий имеется ввиду)



  1. ivan_kov
    28.08.2018 21:35

    Интересно, как обстаят дела у kivy c Rx?


    1. HeaTTheatR Автор
      28.08.2018 21:36

      Что есть Rx? Rx Java?


    1. HeaTTheatR Автор
      28.08.2018 21:43

      У всех классов Kivy реализован метод on_. Вам вообще не надо об этом думать, потому что


      class MyWidget(Widget):
          name = StringProperty()

      Теперь вам доступен метод on_name, который будет вызываться при каждом изменении поля name


      class MyWidget(Widget):
          ...
      
          def on_name(instance_name, property_name):
              ...


      1. ivan_kov
        28.08.2018 22:41

        Отслеживание изменений значения поля — только одино из полезных использований Rx. Еще упрощается асинхронная работа с операциями ввода-вывода, с потоками (thread), ну и с цепью асинхронных событий.
        Да, забыл самое главное: спасибо за интересную статью!


        1. HeaTTheatR Автор
          28.08.2018 23:49

          Еще упрощается асинхронная работа с операциями ввода-вывода, с потоками (thread), ну и с цепью асинхронных событий.

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


  1. ivan_kov
    28.08.2018 22:43

    Позволю себе еще один вопрос.
    У меня начинается новый проект под Android, рассматриваю возможность спрыгнуть с Java.
    Очень интересно узнать вызывает ли затруднения реализация операций в фоне (работа Service, JobService) и обработка каких-либо Intent-ов в BroadcastReceiver?


    1. HeaTTheatR Автор
      28.08.2018 23:54

      С работой в фоне нет проблем. Но есть одно "Но". Если вы перезагрузите смартфон, при его последующем включении сервис вашего приложения не запустится. То есть нужно будет хотя бы раз запустить ваше приложение, чтобы дать старт сервису. Потом можете его убивать — сервис продолжает жить. До следующей перезагрузки. В этой статье автор утверждает обратное: можно запускать сервис Kivy со стартом смартфона. Но мне это проверить не удалось, потому что я так и не смог собрать пакет по этим гайдам.


  1. Shatten55
    29.08.2018 23:43

    Тоже пишем свои приложения на Kivy. Очень понравился именно своими возможности из «конструктора» собрать все, что хочется самому.
    Можно небольшой вопрос, столкнулся с проблемой мультипроцессности (библиотека multiprocessing, не threading) после сборки PyInstaller-ом в Windows. Через некоторое время зависает главный процесс с Kivy. Долго искал решение, так и не нашел. Решил проблему написанием приложения на threading. Может быть слышали о таких проблемах?


    1. HeaTTheatR Автор
      29.08.2018 23:46

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