Pull to refresh

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

Reading time16 min
Views20K

Приветствую всех! Как только дым от жаркой дискуссии в комментариях к моей статье 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.
Tags:
Hubs:
Total votes 20: ↑15 and ↓5+10
Comments33

Articles