Kivy. Xamarin. React Native. Три фреймворка — один эксперимент


    Приветствую всех! Как только дым от жаркой дискуссии в комментариях к моей статье 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
      На волне хайпа могли бы и Flutter захватить
        0

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

        0
        После того как разочаровался в мобильной 1с ради интереса стал посматривать в сторону нативной и кроссплатформенной разработки под мобилки.
        Посматривал на киви, ибо питон нравится своей выразительностью, но все же фреймворк мне чем то не нравился. Язык разметки, слабый набор стандартных виджетов, скорость (особенно старта, да и в целом), начал больше смотреть в сторону нативной разработки.
        А вот недавно наткнулся на довольно интересную вещь: flutter+dart. Пока только изучаю, но выглядит довольно вкусно и на мой взгляд перспективно.
        Язык довольно приятен и выразителен, компилируемый в натив, типы, интересная система создания виджетов, много стандартных виджетов выглядящих нативно, по заявлениям авторов лишен некоторых проблем, что в теории позволит выиграть по скорости анимаций и подобного у разработки на java под андроид, вроде как будет основным фреймворком под фуксию, и в последнее время заметны телодвижения гугла по продвижению.
        Пока единственный минус что вижу — малое коммьюнити.
          0
          Язык разметки, слабый набор стандартных виджетов
          Скажите, в чём сильные стороны других языков разметки? Помимо того, что Kv Language решает все существующие задачи в плане UI, помимо этого данных язык разметки всё ещё является Python и поддерживает конструкции этого языка прямо в разметке. В чём слабость? Каких виджетов вам не хватило?
            0
            Мне в принципе не нравится наличие языков разметки)
              0

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

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

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

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

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

            Это вы на второе пришествие Христа Fuchsi'yu намекаете?:-) Меня вот тоже занимает этот вопрос.
              +1
              В части перспективности да, ходят в сети всякие слухи и подозрения. А вот вкусно и без этого все выглядит) По крайней мере точно хотя бы один-два проектика для себя сделать попробую, и дарт понравился как язык, и архитектура заложенная во фреймворк.
            +1
            Спасибо за обзор. Мы сейчас тоже находимся на стадии выбора инструмента кроссплатформеной разработки. Пока остановились на Xamarin (т.к у нас в основном .net разработчики) но и периодически поглядываем в сторону других инструментов.
            Вот первое что бы хотелось слышать это примеры успешных крупных проектов, в которых применили данный инструмент, а уж потом сравнение кода.
              0

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

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

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

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

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

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

                          0
                          Как подтвердили в комментариях второй части статьи, сборка приложения под iOS невозможна без MacOS и XCode. Это часть политики Apple.
                        0
                        Но всё равно для этого нужен mac в качестве build агента.
                      0

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

                        0

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

                          0
                          Присоединяюсь, ещё очень интересно какой именно комментарий имеется ввиду)
                        0
                        Интересно, как обстаят дела у kivy c Rx?
                          0

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

                            0

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


                            class MyWidget(Widget):
                                name = StringProperty()

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


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

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

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

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

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

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

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

                              Самое читаемое