Время на прочтение
4 мин
Количество просмотров 131K
В этой статье я хотел бы рассказать о том, как создать полноценное приложение на Python для Android. Нет, это не очередной мануал для создания скрипта для sl4a, это мануал по созданию полноценного приложения с UI, возможностью собрать apk и выложить на Android Market. Заодно я хотел бы похвалиться своим первым приложением на google.play, это не hello world, а полезное приложение для фотографов, хотя и узко специализированное.
Начну рассказ пожалуй с самого приложения и завершу рассказам о том как оно делалось.
isortViewer
Мне как фотографу приходится тратить много времени на сортировку и отбор фотографий. Не всегда хочется сидеть за рабочим компьютером, когда под рукой есть ноутбук, смартфон или планшет, но копировать туда десятки гигабайт raw или jpeg файлов, а потом синхронизировать с рабочим компьютером — сомнительное удовольствие. Именно поэтому я создал программы isortManager и isortViewer, которые значительно облегчили мне работу по сортировке и отбору фотографий. Теперь заниматься отбором и сортировкой фотографий я могу лежа на
пляже
диване или скучая в пробке
Копипаст описания и пара скриншотов:
isortViewer — программа для фотографов, которая позволяет легко и быстрой отбирать и сортировать тысячи фотографий (raw или jpg) с фотосессий на ваших android смартфонах и планшетах. При этом, нет необходимости копировать десятки гигабайт jpg или raw файлов на устройство. Просто воспользуйтесь бесплатной программой isortManager для компьютера, которая сохранит все в один небольшой файл проекта:
1. Скачайте и запустите isortManager с официального сайта;
2. Создайте проект и добавьте папки с фотографиями. Поддерживаются форматы: jpg и raw (cr2, nef, orf и т.д.). В итоге у вас получится небольшой файл проекта (примерно 150 мб на несколько тысяч фотографий, будь то jpg или raw формат);
3. Скопируйте на ваше android устройство файл проекта, откройте в isortViewer. Вы можете помечать фотографии «на удаление», а так же, ставить рейтинги от 1 до 5 звезд;
4. После этого, скопируйте файл проекта обратно и примените изменения в isortManager. Кроме удаления выбранных фотографий, вы можете копировать или перемещать отмеченные фотографии. Например, фотографии с «5 звезд» можно скопировать в папку «шедевры», а «1 звезда» переместить в папку «хлам».
Приложение абсолютно бесплатно и доступно на google.play, однако донейты приветствуются.
Исходники доступны на официальном сайте!
Как это делалось
Так как
python лучший язык программирования в мире
кроме python я ничего не знаю, решено было написать приложение на python.
isortManager
С isortManager для PC проблем не возникло, создание GUI сборки под windows уже давно обкатано и работает он очень просто: с помощью dcraw выдирает jpg превьюшки из raw файлов, ресайзит из с помощью PIL и складывает в один файл с достаточно простой структурой. Был изобретен свой весолипед с контейнером для хранения фотографий и метаданных (полный путь к фотографии, отметка «на удаление» и отметка рейтинга), так как, например, zip или tar формат, не могут изменять один файл в архиве (метаданные), требую перепаковки всех файлов. Файлы просто пишутся один за другим подряд и в блоке метаданных (обычный repr питоновского словаря) в конце сохраняется смещения начала файлов, плюс в конце файла пишется размер блока метаданных. GUI написано на Tkinter (люблю я его за быстроту написания и за малый размер итоговой сборки), вот собственно скриншот:
И да, все это работает и на linux, и даже быстрее чем на windows )
isortViewer для android
Всего я нашел два способа заставить python приложение работать как полноценное приложение на android, это pygame for android и проект kivy.
Pygame — более низкоуровневый, все UI пришлось бы рисовать вручную, что отняло бы много времени, поэтому был выбран фреймворк kivy. Итак:
Kivy
Это замечательный фрейморк для написания приложение для windows, linux, MacOS, android и iOS. Поддерживается мультитач, UI рисутется через OpenGL, значит должно работать аппаратное ускорение. Увидеть работу виджетов можно установив демонстрационное приложение Kivy Showcase. Есть возможность использования некоторых платформо специфичных функций, например, вибро или акселерометр, с помощью модуля android.
Для разработки я бы рекомендовал использовать linux, тем более, apk собираются именно в этой OS.
Hello World выглядит так:
import kivy
kivy.require('1.0.6') # replace with your current kivy version !
from kivy.app import App
from kivy.uix.button import Button
class MyApp(App):
def build(self):
return Button(text='Hello World')
if __name__ == '__main__':
MyApp().run()
Причем этот код будет работать на всех заявленных платформах. Пропадает необходимость в тестировании приложения в эмуляторе. Достаточно запустить скрипт на исполнение в своей любимой IDE и увидеть результат на экране компьютера, без задержек на компиляцию, запуск эмулятора и пр. Если вы все же хотите увидеть как это будет выглядеть непосредственно на устройстве, просто установите Kivy Launcher, скопируйте файлы проекта на карту памяти и запустите. Дебажить при этом можно при помощи adb logcat.
Если вы используете библиотеку android, которой нет на PC, но хочется запускать приложение не только на android, воспользуйте такой конструкцией:
try:
import android
except ImportError:
android=None
...
if android:
android.vibrate(0.05)
Сборка apk достаточно проста и описана на этой странице. После сборки релиза, достаточно подписать свое приложение (я использовал этот мануал) и выложить в google play.
Плюсы kivy:
- Быстрая разработка под различные платформы, практически без доработки кода.
- Доступен широкий выбор виджетов
- Высокая скорость работы. Весь ресурсоемкий вынесен в С модули. Сам интерпретатор python на android работает нативно.
- Фремворк включает в себя множество инструментов, например анимация, кеширование и пр.
- upd: Доступ к камере, буферу обмена, микрофону. Написать свой видеоплеер можно буквально за 20 строк.
Минусы kivy:
- Большой размер apk файла. Проект с 300 кб ресурсов (скрипты, графика) собирается в 7 мб apk. Хотя, думаю, есть возможность это как то оптимизировать.
- Невозможность (пока), восстанавливать работу после сворачивания — приложение закрывается
В следующей своей статье, я расскажу более подробно о процессе написания приложения «с нуля», расжевывая каждую строчку кода.
Хотелось бы сразу пресечь холивары на тему «для android только java, python не нужен». Я считаю, не важно, какие технологии «под капотом», главное чтоб приложение было качественным.
Сфера применения kivy может быть огромной. Сейчас на моем счету несколько приложений, написанных под заказ, где python с фремворком kivy показал себя с хорошей стороны. Например, ровно за один час, было написано приложение, для сети сервисных центров. Приложение работает в режиме киоска и установлено на дешевые китайские планшеты, которые висят в холле. Клиент набирает номер заказа, далее по WiFi планшет соединяется с сервером и сообщает статус заказа.
В наши дни каждый разработчик может столкнуться с необходимостью работы над мобильным или веб-приложением на Python. В Python нет встроенных инструментов для мобильных устройств, тем не менее существуют пакеты, которые можно использовать для создания мобильных приложений. Это Kivy, PyQt и даже библиотека Toga от Beeware.
Содержание
- Принципы работы фреймворка Kivy Python
- Установка Kivy
- Работа с виджетами в Kivy
- Запуск программы «Hello, Kivy!»
- Отображение виджета Image в Kivy Python
- Разметка (Layout) в UI Kivy
- Добавление событий в Kivy
- Использование языка дизайна KV
- Создание приложения Kivy Python
- Создаем apk приложения для Android на Python
- Создание приложений для iPhone (iOS) на Python
- Создание exe приложений для Windows на Python используя Kivy
- Создание приложений для macOS на Python используя Kivy
Библиотеки являются основными элементами мобильного мира Python. Однако, говоря о Kivy, нельзя игнорировать преимущества данного фреймворка при работе с мобильными приложениями. Внешний вид приложения автоматически подстраивается под все платформы, разработчику при этом не нужно компилировать код после каждой поправки. Кроме того, здесь для создания приложений можно использовать чистый синтаксис Python.
В руководстве будут разобраны следующие темы:
- Работа с виджетами Kivy;
- Планировка UI и лейауты;
- Добавление событий;
- Использование языка KV;
- Создание приложения-калькулятора;
- Упаковка приложения для iOS, Android, Windows и macOS.
Разбор данного руководства предполагает, что читатель знаком с объектно-ориентированным программированием. Для введения в курс дела можете просмотреть статью об Объектно-ориентированном программировании (ООП) в Python 3.
Приступим!
Принципы работы фреймворка Kivy Python
Kivy был создан в 2011 году. Данный кросс-платформенный фреймворк Python работает на Windows, Mac, Linux и Raspberry Pi. В дополнение к стандартному вводу через клавиатуру и мышь он поддерживает мультитач. Kivy даже поддерживает ускорение GPU своей графики, что во многом является следствием использования OpenGL ES2. У проекта есть лицензия MIT, поэтому библиотеку можно использовать бесплатно и вкупе с коммерческим программным обеспечением.
Во время разработки приложения через Kivy создается интуитивно понятный интерфейс (Natural user Interface), или NUI. Его главная идея в том, чтобы пользователь мог легко и быстро приспособиться к программному обеспечению без чтения инструкций.
Kivy не задействует нативные элементы управления, или виджеты. Все его виджеты настраиваются. Это значит, что приложения Kivy будут выглядеть одинаково на всех платформах. Тем не менее, это также предполагает, что внешний вид вашего приложения будет отличаться от нативных приложений пользователя. Это может стать как преимуществом, так и недостатком, все зависит от аудитории.
Установка Kivy
У Kivy есть множество зависимостей, поэтому лучше устанавливать его в виртуальную среду Python. Можно использовать встроенную библиотеку Python venv или же пакет virtualenv.
Виртуальная среда Python создается следующим образом:
$ python3 —m venv my_kivy_project |
По ходу данного действия исполняемый файл Python 3 будет скопирован в папку под названием my_kivy_project
, куда также будут добавлено несколько других папок.
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Telegram Чат & Канал
Вступите в наш дружный чат по Python и начните общение с единомышленниками! Станьте частью большого сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Для использования виртуальной среды ее нужно активировать. На Mac или Linux это можно сделать, выполнив следующую команду, будучи внутри папки my_kivy_project
:
Команда для Windows точно такая же, но активировать скрипт нужно в другом месте — через папку Scripts
, а не bin
.
После активации виртуальной среды Python можно запустить pip для установки Kivy. На Linux и Mac нужно выполнить следующую команду:
$ python —m pip install kivy |
Инсталляция на Windows несколько сложнее. В официальной документации фреймворка изучите пункт, касающийся установки Kivy на Windows. Пользователи Mac также могут скачать файл dmg
и установить Kivy данным образом.
В случае возникновения проблем во время установки Kivy на вашу платформу изучите дополнительные инструкции, с которыми можно ознакомиться через страницу загрузки.
Работа с виджетами в Kivy
Виджеты — это отображаемые на экране элементы управления, которыми пользователь может оперировать. Любой инструментарий графического интерфейса пользователя поставляется с набором виджетов. Типичными представителями виджетов, что вы не раз использовали, являются кнопки, выпадающие списки и вкладки. Внутри фреймворка Kivy встроено много виджетов.
Запуск программы «Hello, Kivy!»
Принцип работы Kivy можно уловить, взглянув на следующее приложение «Hello, World!»:
from kivy.app import App from kivy.uix.label import Label class MainApp(App): def build(self): label = Label(text=‘Hello from Kivy’, size_hint=(.5, .5), pos_hint={‘center_x’: .5, ‘center_y’: .5}) return label if __name__ == ‘__main__’: app = MainApp() app.run() |
Каждому приложению Kivy требуется создать подкласс App
и переопределить метод build()
. Сюда вы помещаете код UI или вызываете другие функции, которые определяют код UI. В данном случае создается виджет Label
и передается text
, size_hint
и pos_hint
. Последние два аргумента не обязательны.
size_hint
говорит Kivy о размерах что нужно использовать при создании виджета. Используются два числа:
- Первое число
x
указывает на размер ширины элемента управления. - Второе число
y
указывает на размер высоты элемента управления.
Значение обоих чисел должно быть в промежутке между 0
и 1
. Значение по обоих показателей по умолчанию равно 1
. Также можно задействовать pos_hint
, что используется для позиционирования виджета. В коде, размещенном выше, указывается, что виджет должен быть размещен в центре осей x
и y
.
Для запуска приложения нужно инициализировать класс MainApp
и вызвать метод run()
. После этих действий на экране появится следующее:
Kivy также выводит в stdout
довольно много текста:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
[INFO ] [Logger ] Record log in /home/mdriscoll/.kivy/logs/kivy_19—06—07_2.txt [INFO ] [Kivy ] v1.11.0 [INFO ] [Kivy ] Installed at «/home/mdriscoll/code/test/lib/python3.6/site-packages/kivy/__init__.py» [INFO ] [Python ] v3.6.7 (default, Oct 22 2018, 11:32:17) [GCC 8.2.0] [INFO ] [Python ] Interpreter at «/home/mdriscoll/code/test/bin/python» [INFO ] [Factory ] 184 symbols loaded [INFO ] [Image ] Providers: img_tex, img_dds, img_sdl2, img_gif (img_pil, img_ffpyplayer ignored) [INFO ] [Text ] Provider: sdl2([‘text_pango’] ignored) [INFO ] [Window ] Provider: sdl2([‘window_egl_rpi’] ignored) [INFO ] [GL ] Using the «OpenGL» graphics system [INFO ] [GL ] Backend used <sdl2> [INFO ] [GL ] OpenGL version <b‘4.6.0 NVIDIA 390.116’> [INFO ] [GL ] OpenGL vendor <b‘NVIDIA Corporation’> [INFO ] [GL ] OpenGL renderer <b‘NVS 310/PCIe/SSE2’> [INFO ] [GL ] OpenGL parsed version: 4, 6 [INFO ] [GL ] Shading version <b‘4.60 NVIDIA’> [INFO ] [GL ] Texture max size <16384> [INFO ] [GL ] Texture max units <32> [INFO ] [Window ] auto add sdl2 input provider [INFO ] [Window ] virtual keyboard not allowed, single mode, not docked [INFO ] [Base ] Start application main loop [INFO ] [GL ] NPOT texture support is available |
Это может быть полезно для отладки приложения.
Далее добавим виджет Image
и посмотрим, чем он отличается от Label
.
Отображение виджета Image в Kivy Python
В Kivy есть несколько видов виджетов, связанных с изображениями. Для загрузки картинок с жесткого диска можно задействовать Image
, а при использовании адреса URL подойдет AsyncImage
. К следующем примере берется стандартный класс Image
:
from kivy.app import App from kivy.uix.image import Image class MainApp(App): def build(self): img = Image(source=‘/path/to/real_python.png’, size_hint=(1, .5), pos_hint={‘center_x’:.5, ‘center_y’:.5}) return img if __name__ == ‘__main__’: app = MainApp() app.run() |
В данном коде импортируется Image
из подпакета kivy.uix.image
. Класс Image
принимает много разных параметров, однако единственным для нас нужным является source
, что указывает Kivy, какое изображение должно быть загружено. Здесь передается полный путь к выбранному изображению. Оставшаяся часть кода такая же, как и в прошлом примере.
После запуска кода должно выводиться нечто подобное:
Текст из предыдущего примера был заменен картинкой.
Теперь рассмотрим, как добавить и оптимально расположить несколько виджетов в приложении.
Разметка (Layout) в UI Kivy
У каждого фреймворка есть свой собственный метод для размещения виджетов. К примеру, в wxPython используются классификаторы, а в Tkinter будет задействован лейаут, или менеджер геометрии. В Kivy за это отвечают Лейауты (Layouts). Доступно несколько различных типов Лейаутов. Чаще всего используются следующие виды:
- BoxLayout;
- FloatLayout;
- GridLayout.
Найти полный список доступных Лейаутов можно в документации Kivy. Рабочий исходный код можно найти в kivy.uix
.
Рассмотрим BoxLayout
на примере следующего кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import kivy import random from kivy.app import App from kivy.uix.button import Button from kivy.uix.boxlayout import BoxLayout red = [1,0,0,1] green = [0,1,0,1] blue = [0,0,1,1] purple = [1,0,1,1] class HBoxLayoutExample(App): def build(self): layout = BoxLayout(padding=10) colors = [red, green, blue, purple] for i in range(5): btn = Button(text=«Button #%s» % (i+1), background_color=random.choice(colors) ) layout.add_widget(btn) return layout if __name__ == «__main__»: app = HBoxLayoutExample() app.run() |
Здесь из kivy.uix.boxlayout
импортируется модуль BoxLayout
и затем устанавливается. После этого создается список цветов, которые представляют собой цвета RGB (Red-Blue-Green).
В конечном итоге формируется цикл для range из 5, результатом чего является кнопка btn
для каждой итерации. Сделаем вещи немного интереснее и поставим в качестве фона кнопки background_color
случайный цвет. Теперь можно добавить кнопку в лейаут при помощи layout.add_widget(btn)
.
После запуска кода выведется нечто подобное:
Здесь представлены 5 кнопок, окрашенных случайным образом, по одной для каждой итерации цикла for.
Во время создания лейаута следует учитывать следующие аргументы:
- padding: Отступ
padding
между лейаутом и его дочерними элементами уточняется в пикселях. Для этого можно выбрать один из трех способов:- Список из четырех аргументов:
[padding_left, padding_top, padding_right, padding_bottom]
- Список из двух аргументов:
[padding_horizontal, padding_vertical]
- Один аргумент:
padding=10
- Список из четырех аргументов:
- spacing: При помощи данного аргумента добавляется расстояние между дочерними виджетами.
- orientation: Позволяет изменить значение
orientation
дляBoxLayout
по умолчанию — с горизонтального на вертикальное.
Добавление событий в Kivy
Как и многие другие инструментарии GUI, по большей части Kivy полагается на события. Фреймворк отзывается на нажатие клавиш, кнопки мышки или прикосновение к сенсорному экрану. В Kivy задействован концепт Часов (Clock), что дает возможность создать своего рода график для вызова определенных функций в будущем.
В Kivy также используется концепт Свойств (Properties), что работает с EventDispatcher. Свойства помогают осуществить проверку достоверности. Они также запускают события, когда виджет меняет размер или позицию.
Добавим событие для кнопки из предыдущего кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from kivy.app import App from kivy.uix.button import Button class MainApp(App): def build(self): button = Button(text=‘Hello from Kivy’, size_hint=(.5, .5), pos_hint={‘center_x’: .5, ‘center_y’: .5}) button.bind(on_press=self.on_press_button) return button def on_press_button(self, instance): print(‘Вы нажали на кнопку!’) if __name__ == ‘__main__’: app = MainApp() app.run() |
В данном коде вызывается button.bind()
, а событие on_press
ссылается на MainApp.on_press_button()
.
Этот метод неявно принимает экземпляр виджета, который является самим объектом кнопки. Сообщение будет выводиться на stdout
всякий раз при нажатии пользователем на кнопку.
Использование языка дизайна KV
Kivy предоставляет язык дизайна KV, что можно использовать в приложениях Kivy. Язык KV позволяет отделить дизайн интерфейса от логики приложения. Он придерживается принципа разделения ответственности и является частью архитектурного паттерна Модель-Представление-Контроллер (Model-View-Controller). Предыдущий пример можно обновить, используя язык KV:
from kivy.app import App from kivy.uix.button import Button class ButtonApp(App): def build(self): return Button() def on_press_button(self): print(‘Вы нажали на кнопку!’) if __name__ == ‘__main__’: app = ButtonApp() app.run() |
С первого взгляда данный код может показаться несколько странным, так как кнопка Button
создается без указания атрибутов или привязывания к ним событий. Здесь Kivy автоматически ищет файл с таким же названием, что и у класса, только строчными буквами и без части App
в названии класса.
В данном случае названием класса является ButtonApp
, поэтому Kivy будет искать файл button.kv
. Если такой файл существует, и он также форматирован должным образом, тогда Kivy использует его при загрузке UI. Попробуйте создать такой файл и добавить следующий код:
<Button>: text: ‘Press me’ size_hint: (.5, .5) pos_hint: {‘center_x’: .5, ‘center_y’: .5} on_press: app.on_press_button() |
Действия каждой строки:
- Строка 1 соответствует вызову
Button
в коде Python. Kivy должен осмотреть инициализированный объект для определения кнопки; - Строка 2 устанавливает
text
кнопки; - Строка 3 устанавливает ширину и высоту при помощи
size_hint
; - Строка 4 устанавливает позицию кнопки через
pos_hint
; - Строка 5 устанавливает обработчик событий
on_press
. Для указания Kivy места обработчика событий используетсяapp.on_press_button()
. Здесь Kivy будет искать метод.on_press_button()
в классеApplication
.
Вы можете установить все ваши виджеты и лейауты внутри одного или нескольких файлов языка KV. Язык KV также поддерживает импорт модулей Python в KV, создавая динамичные классы, и это далеко не предел. Ознакомиться с полным перечнем его возможностей можно в гиде Kivy по языку KV.
Теперь мы можем приступить к созданию настоящего рабочего приложения.
Создание приложения Kivy Python
Создание чего-то полезное несомненно является отличным способом выучить новый навык. Учитывая данное утверждение, давайте используем Kivy при создании калькулятора, который будет поддерживать следующие операции:
- Сложение;
- Вычитание;
- Умножение;
- Деление.
В данном приложении будет использован набор кнопок в своего рода лейауте. В верхней части также будет специальный блок для вывода операций и их результатов. В итоге калькулятор будет выглядеть следующим образом:
Теперь, когда у нас есть в наличии целевой UI, может составить код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.uix.button import Button from kivy.uix.textinput import TextInput class MainApp(App): def build(self): self.operators = [«/», «*», «+», «-«] self.last_was_operator = None self.last_button = None main_layout = BoxLayout(orientation=«vertical») self.solution = TextInput( multiline=False, readonly=True, halign=«right», font_size=55 ) main_layout.add_widget(self.solution) buttons = [ [«7», «8», «9», «/»], [«4», «5», «6», «*»], [«1», «2», «3», «-«], [«.», «0», «C», «+»], ] for row in buttons: h_layout = BoxLayout() for label in row: button = Button( text=label, pos_hint={«center_x»: 0.5, «center_y»: 0.5}, ) button.bind(on_press=self.on_button_press) h_layout.add_widget(button) main_layout.add_widget(h_layout) equals_button = Button( text=«=», pos_hint={«center_x»: 0.5, «center_y»: 0.5} ) equals_button.bind(on_press=self.on_solution) main_layout.add_widget(equals_button) return main_layout |
Калькулятор работает следующим образом:
- В строках с 8 по 10 создается список
operators
и несколько полезных значений,last_was_operator
иlast_button
, которые будут использованы чуть позже. - В строках с 11 по 15 создается лейаут верхнего уровня
main_layout
, к нему также добавляется виджет только для чтенияTextInput
. - В строках с 16 по 21 создается вложенный список из списков, где есть большая часть кнопок для калькулятора.
- В строке 22 начинается цикл for для кнопок. Для каждого вложенного списка делается следующее:
- В строке 23 создается
BoxLayout
с горизонтальной ориентацией. - В строке 24 начинается еще один цикл
for
для элементов вложенного списка. - В строках с 25 по 39 создаются кнопки для ряда и связываются обработчиком событий, после чего кнопки добавляются к горизонтальному
BoxLayout
из строки 23. - В строке 31 этот лейаут добавляется к
main_layout
.
- В строке 23 создается
- В строках с 33 по 37 создается кнопка равно (=) и привязывается к обработчику событий, после чего она добавляется к
main_layout
.
Далее создается обработчик событий .on_button_press()
. Код будет выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
def on_button_press(self, instance): current = self.solution.text button_text = instance.text if button_text == «C»: # Очистка виджета с решением self.solution.text = «» else: if current and ( self.last_was_operator and button_text in self.operators): # Не добавляйте два оператора подряд, рядом друг с другом return elif current == «» and button_text in self.operators: # Первый символ не может быть оператором return else: new_text = current + button_text self.solution.text = new_text self.last_button = button_text self.last_was_operator = self.last_button in self.operators |
Почти все виджеты приложения вызывают .on_button_press()
. Это работает следующим образом:
- Строка 41 принимает аргумент
instance
, в результате чего можно узнать, какой виджет вызвал функцию. - Строки между 42 и 43 извлекают и хранят значения
solution
иtext
кнопки. - Строки c 45 по 47 проверяют, на какую кнопку нажали. Если пользователь нажимает с, тогда очищается
solution
. В противном случае используется утверждениеelse
. - Строка 49 проверяет, было ли у решения предыдущее значение.
- Строки с 50 по 52 проверяют, была ли последняя нажатая кнопка оператором. Если да, тогда
solution
обновляться не будет. Это необходимо для предотвращения создания двух операций в одном ряду. К примеру,1 * /
будет недействительным утверждением. - Строки с 53 по 55 проверяют, является ли первый символ оператором. Если да, тогда solution обновляться не будет, так как первое значение не может быть значением оператора.
- Строки с 56 по 58 переходят к условию
else
. Если никакое из предыдущих значений не найдено, тогда обновляетсяsolution
. - Строка 59 устанавливает
last_button
к метке последней нажатой кнопки. - Строка 60 устанавливает
last_was_operator
к значениюTrue
илиFalse
в зависимости от того, был символ оператором или нет.
Последней частью кода будет .on_solution()
:
def on_solution(self, instance): text = self.solution.text if text: solution = str(eval(self.solution.text)) self.solution.text = solution |
Здесь берется текущий текст из solution
и используется встроенный в Python eval()
для исполнения. Если пользователь создал формулу вроде 1+2
, тогда eval()
запустит код и вернет результат. В конце результат устанавливается как новое значение виджета solution
.
На заметку: порой
eval()
бывает опасным, так как он может запустить произвольный код. Многие разработчики избегают его использование именно по этой причине. Тем не менее, ввиду задействования только целых чисел, операторов и точки в качестве вводных данных дляeval()
, в данном контексте его можно использовать безопасно.
При запуске данного кода на рабочем столе компьютера приложение будет выглядеть следующим образом:
Полный текст кода примера калькулятора представлен ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.uix.button import Button from kivy.uix.textinput import TextInput class MainApp(App): def build(self): self.operators = [«/», «*», «+», «-«] self.last_was_operator = None self.last_button = None main_layout = BoxLayout(orientation=«vertical») self.solution = TextInput( multiline=False, readonly=True, halign=«right», font_size=55 ) main_layout.add_widget(self.solution) buttons = [ [«7», «8», «9», «/»], [«4», «5», «6», «*»], [«1», «2», «3», «-«], [«.», «0», «C», «+»], ] for row in buttons: h_layout = BoxLayout() for label in row: button = Button( text=label, pos_hint={«center_x»: 0.5, «center_y»: 0.5}, ) button.bind(on_press=self.on_button_press) h_layout.add_widget(button) main_layout.add_widget(h_layout) equals_button = Button( text=«=», pos_hint={«center_x»: 0.5, «center_y»: 0.5} ) equals_button.bind(on_press=self.on_solution) main_layout.add_widget(equals_button) return main_layout def on_button_press(self, instance): current = self.solution.text button_text = instance.text if button_text == «C»: # Очистка виджета с решением self.solution.text = «» else: if current and ( self.last_was_operator and button_text in self.operators): # Не добавляйте два оператора подряд, рядом друг с другом return elif current == «» and button_text in self.operators: # Первый символ не может быть оператором return else: new_text = current + button_text self.solution.text = new_text self.last_button = button_text self.last_was_operator = self.last_button in self.operators def on_solution(self, instance): text = self.solution.text if text: solution = str(eval(self.solution.text)) self.solution.text = solution if __name__ == «__main__»: app = MainApp() app.run() |
Пришло время разместить приложение в Google Play или в AppStore!
По завершении составления кода вы можете поделиться своим приложением с другими. Хорошим способом сделать это может стать превращение вашего кода в приложения для смартфона на Android. Для этого вначале нужно установить пакет buildozer
через pip
:
Затем создается новая папка, после чего нужно перейти в нее через терминал. Затем выполняется следующая команда:
После этого создается файл buildozer.spec
, который будет использован для конфигурации сборки. К примеру, первые две строчки файла спецификации можно редактировать следующим образом:
[app] # (str) Название вашего приложения title = KvCalc # (str) Название упаковки package.name = kvcalc # (str) Домен упаковки (нужен для упаковки android/ios) package.domain = org.kvcalc |
Не бойтесь посмотреть оставшуюся часть файла для выяснения того, что еще можно поменять.
На данный момент приложение почти готово к сборке, однако для начала нужно установить зависимости для buildozer
. После их установки скопируйте ваше приложение калькулятора в новую папку и переименуйте его в main.py
. Этого требует buildozer
. Если файл будет назван неверно, тогда процесс сборки завершится неудачей.
Теперь можно запустить следующую команду:
$ buildozer —v android debug |
Этап сборки займет время! На моем компьютере на это ушло около 15-20 минут. Здесь все зависит от вашего железа, так что времени может потребоваться еще больше. Расслабьтесь, налейте чашечку кофе или прогуляйтесь. Buildozer
скачает те элементы Android SDK, которые нужны для процесса сборки. Если все идет по плану, тогда в папке bin
появится файл под названием, напоминающим что-то вроде kvcalc-0.1-debug.apk
.
Далее требуется связать телефон Android с компьютером и перенести туда файл apk
. Затем откройте менеджер файлов телефона и кликните на файл apk
. Android должен спросить, хотите ли вы установить приложение. Есть вероятность появления предупреждения, ведь приложение было скачано не из Google Play. Тем не менее, вы по-прежнему сможете установить его.
Вот как выглядит калькулятор, запущенный на Samsung S9:
У buildozer
также есть несколько других команд, которые вы можете использовать. Изучите документацию, чтобы подробнее узнать об этом.
При необходимости добиться более детального управления упаковку можно осуществить через python-for-android
. Здесь это обсуждаться не будет, но если интересно, ознакомьтесь, как еще можно быстро начать проект.
Создание приложений для iPhone (iOS) на Python
Инструкция для сборки приложения для iOS будет немного сложнее, нежели для Android. Для получения последней информации всегда проверяйте обновления официальной документации Kivy.
Вам нужен будет компьютер с операционной системой OS X: MacBook или iMac. На Linux или Windows вы не сможете создать приложения для Apple.
Перед упаковкой приложения для iOS на Mac необходимо выполнить следующие команды:
$ brew install autoconf automake libtool pkg—config $ brew link libtool $ sudo easy_install pip $ sudo pip install Cython==0.29.10 |
После успешной установки нужно скомпилировать при использования следующих команд:
$ git clone git://github.com/kivy/kivy—ios $ cd kivy—ios $ ./toolchain.py build python3 kivy |
Если вы получаете ошибку, где говорится, что iphonesimulator
не найден, тогда поищите способ решение проблемы на StackOverflow, после чего попробуйте запустить команды вновь.
Если вы получаете ошибки SSL, тогда скорее всего у вас не установлен OpenSSL от Python. Следующая команда должна это исправить:
$ cd /Applications/Python 3.7/ $ ./Install Certificates.command |
Теперь вернитесь назад и запустите команду toolchain
опять.
После успешного выполнения всех указанных выше команд можете создать проект Xcode при помощи использования скрипта toolchain
. Перед созданием проекта Xcode переименуйте ваше главное приложение в main.py
, это важно. Выполните следующую команду.
./toolchain.py create <title> <app_directory> |
Здесь должна быть папка под названием title
, внутри которой будет проект Xcode. Теперь можно открыть проект Xcode и работать над ним отсюда. Обратите внимание, что если вы хотите разместить свое приложение на AppStore, вам понадобится создать аккаунт разработчика на developer.apple.com и заплатить годовой взнос.
Создание exe приложений для Windows на Python используя Kivy
Упаковать приложение Kivy для Windows можно при помощи PyInstaller. Если ранее вы никогда не работали с ним, тогда изучите тему использования PyInstaller для упаковки кода Python в исполняемый файл.
Для установки PyInstaller можно использовать pip
:
$ pip install pyinstaller |
Следующая команда упакует ваше приложение:
Команда создаст исполняемый файл Windows, а вместе с ним еще несколько других файлов. Аргумент -w
говорит PyInstaller, что приложение открывается в оконном режиме и не является приложение для командной строки. Если вы хотите, чтобы PyInstaller создал только один исполняемый файл, тогда можете передать в дополнение к -w
аргумент --onefile
.
Создание приложений для macOS на Python используя Kivy
Как и в случае с Windows, для создания исполняемого файла Mac можно также использовать PyInstaller. Единственным условием является запуск следующей команды на Mac:
$ pyinstaller main.py —w —onefile |
Результатом станет один исполняемый файл в папке dist
. Название исполняемого файла будет таким же, как и название файла Python, что был передан PyInstaller.
Если вы хотите уменьшить размер исполняемого файла или использовать в приложении GStreamer, тогда для получения дополнительной информации изучите тему упаковки для macOS.
Заключение
Kivy является действительно интересным фреймворком GUI, что можно использовать для создания пользовательских интерфейсов и мобильных приложений для Android и iOS. Внешне приложения Kivy будут отличаться от нативных приложений выбранной платформы. В том случае, если вы хотите выделяться на фоне конкурентов, это может быть выгодным преимуществом.
В данном руководстве были рассмотрены основы Kivy, среди которых стоит выделить добавление виджетов, привязку событий, планировку виджетов и лейауты, а также использование языка KV. В результате мы получили рабочее приложение Kivy и рассмотрели способы его переноса на другие платформы, в том числе мобильные.
В Kivy есть множество виджетов и концептов, которые не были рассмотрены в статьи. Для дальнейшего изучения темы можете изучить официальный сайт Kivy, где размещены разнообразные руководства, примеры приложений и многое другое.
Рекомендации
Для дальнейшего изучения Kivy ознакомьтесь со следующими ресурсами:
- Гид программирования на Kivy
- Документация по упаковке приложений Kivy
- Сборка приложений GUI через Python
Чтобы посмотреть, как создать приложение с графическим интерфейсом при использовании другого GUI фреймфорка Python, можете ознакомиться со статьями о wxPython.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»
Рассказывает Александр Тейлор, разработчик проекта Kivy
В последнее время появляется большое количество ресурсов по разработке на Python под Android. Все чаще упоминается предназначенный для этой задачи фреймворк Kivy (и его ответвления), ведь он является одним из самых проверенных временем и надежных проектов в этой области. Тем не менее, одну важную деталь незаслуженно обделяют вниманием — что вообще мы сможем делать после того, как Python станет запускаться на устройстве? Есть ли какие-то ограничения? Все ли библиотеки можно подключать? Возможно ли делать все то же, что и при написании приложения на Java? Данные вопросы волнуют многих, и они рассматриваются и решаются в рамках проекта Kivy. В этой статье я постараюсь рассмотреть наиболее интересные и важные детали.
Python-for-android
Прежде всего давайте посмотрим на то, с помощью чего Python получает возможность работать под Android — инструмент, названный, как ни странно, python-for-android. Его основная функция состоит в том, чтобы создать дистрибутив — папку проекта, содержащую все необходимое для запуска вашего приложения. А точнее, сам интерпретатор, Kivy и библиотеки, от которых он зависит: Pygame, SDL и несколько других. Также дистрибутив включает в себя загрузчик Java, отображающий OpenGL и выступающий в качестве посредника между Kivy и операционной системой. Затем вы добавляете ко всему этому свои скрипты, настройки вроде иконки и имени, компилируете с помощью Android NDK и вуаля — APK с вашим приложением готов!
И это всего лишь базовая процедура, на самом деле сгенерированный пакетный файл может включать (и включает) в себя гораздо больше. Вместе со всем прочим в APK вшивается большая часть стандартной библиотеки, а любой сторонний модуль, написанный на Python, может быть легко добавлен — все так же, как и при разработке десктоп-приложений. Добавка модулей с компилируемыми компонентами тоже не вызывает трудностей, необходимо лишь указать, как их нужно собирать. Как правило, это не представляет собой ничего сложного, достаточно лишь поставить пару галочек перед запуском процедуры сборки, хотя в редких отдельных случаях могут понадобиться дополнительные действия. Python-for-android уже включает в себя указания для компиляции таких популярных модулей, как: numpy, sqlite3, twisted и даже django!
Вышеописанные принципы лишь в общих словах объясняют, как работает python-for-android. В любой момент вы можете получить больше информации на данную тему, заглянув в документацию Kivy. Я рекомендую вам Buildozer — надстройку для python-for-android, предоставляющую собой удобный интерфейс и автоматическое разрешение некоторых зависимостей. Мы стараемся сделать так, чтобы написанная выше цепочка действий использовалась не только в Kivy, но и в других проектах. Основной процесс сборки останется таким же, но нужда в загрузчике Java отпадет, так как он необходим только для поддержки некоторых специфичных нужд фреймворка.
Обращение к Android API с помощью PyJNIus
Взаимодействие с Android API: получение информации с сенсоров, создание уведомлений, вибрация, пауза и перезапуск, да что угодно — важная часть вашего приложения. Kivy за вас позаботится об основном, но многими вещами вы захотите управлять сами. Для этого создан PyJNIus — инструмент, автоматически оборачивающий код на Java в интерфейс Python.
В качестве простого примера приведем программу, которая заставит телефон вибрировать на протяжении 10 секунд:
from jnius import autoclass
# Для начала нам нужна ссылка на Java Activity, в которой
# запущено приложение, она хранится в загрузчике Kivy PythonActivity
PythonActivity = autoclass('org.renpy.android.PythonActivity')
activity = PythonActivity.mActivity
Context = autoclass('android.content.Context')
vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE)
vibrator.vibrate(10000) # аргумент указывается в миллисекундах
Если вы знакомы с Android API, то без труда заметите, что код выше очень похож на аналогичный на Java — PyJNIus просто позволяет нам обращаться к тому же API, но прямо из Python. Большая часть Android API может быть вызвана подобным образом, что позволяет достичь того же функционала, что и при разработке на Java.
Главный минус PyJNIus в том, что он требует неплохого понимания структуры Android API, а код выходит громоздким, хотя его эквивалент на Java выглядит точно так же. Для решения этой проблемы Kivy включает в себя Plyer.
Plyer: кроссплатформенное API для платформоспецифичных задач
Проект Plyer ставит себе цель создать простой «питоничный» интерфейс для функций, которые присутствуют на большинстве платформ. Например, код выше легким движением руки превращается в…
from plyer.vibrator import vibrate
vibrate(10) # В Plyer аргументы указываются в секундах
Более того, написанный код попытается выполнить свою задачу на всех поддерживаемых Plyer платформах — на данный момент это: Android, iOS, Linux, Windows и OS X (для iOS также существует аналог PyJNIus, называемая PyOBJus). На самом деле, вибрация — не самый лучший пример, потому что сейчас она реализована только для Android, но такие функции как проверка уровня заряда батареи:
from plyer import battery; print(battery.status)
или text-to-speech:
from plyer import tts; tts.speak('hello world')
— работают как в десктопных, так и в мобильных приложениях, а получение данных с компаса/гироскопа и отправка SMS без проблем реализуются на Android и iOS.
Plyer находится на начальной стадии развития, так что любая помощь в разработке приветствуется. Также, мы участвуем с ним в Google Summer of Code в этом году.
Не только ради Kivy
Все вышеперечисленные инструменты были разработаны для нашего фреймворка, но на самом деле они больше предназначены для разработки под Python в целом. В Plyer мы специально избегаем какой-либо зависимости от Kivy, а PyJNIus нужен лишь для доступа к Android JNI. Искренне надеемся, что эти инструменты станут полезны для любого, кто пишет на Python под Android. Вы уже можете попробовать PyJNIus, используя QPython. Python-for-android больше завязан на взаимодействии с Kivy, но мы будем рады обсудить этот вопрос.
Многое можно реализовать при разработке на Android с помощью Python, несмотря на все различия с Java, которая предназначена для этого, но эти возможности могут быть расширены еще больше в ближайшем будущем. И если вы заинтересовались описанными выше проектами, то самое время присоединиться к нашей команде!
Перевод статьи «Python on Android»
Разработка под Android, Python, Разработка игр
Рекомендация: подборка платных и бесплатных курсов Unity — https://katalog-kursov.ru/
Некоторое время тому назад я решил попробовать написать что-то на Python под Android. Такой странный для многих выбор обусловлен тем, что я люблю Python и люблю Android, а ещё люблю делать необычное (ну хорошо, не самое обычное). В качестве фреймворка был выбран Kivy — фактически, безальтернативный вариант, но он мне очень понравился. Однако, по нему не так уж много информации (нет, документация отличная, но иногда её недостаточно), особенно на русском языке, а некоторые вещи хоть и можно реализовать, но их то ли никто раньше не делал, то ли не счёл нужным поделиться информацией. Ну а я счёл И этот пост тому результатом.
Под катом я расскажу обо всех этапах разработки, о том, как развивалась простая идея и как для этого приходилось искать новые возможности, о возникших подводных камнях и багах, о неочевидных решениях и устаревшей документации Цель — описать в одном тексте основные пункты, чтобы человеку, решившему написать что-то немного сложнее игры Pong из официального туториала, не приходилось перерывать официальный форум поддержки и StackOverflow и тратить часы на то, что делается за пару минут, если знаешь, как.
0. Если вы впервые слышите о Kivy…
… то всё зависит от того, любите ли вы Python и Android, и интересно ли вам в этом разобраться. Если нет — проще забить А если да, то начать нужно с официальной документации, гайдов, и уже упомянутого официального туториала по игре Pong — это даст базовое представление о фреймворке и его возможностях. Я же не буду останавливаться на столь тривиальных вещах (тем более, для понимания базовых принципов туториал отлично подходит) и сразу пойду дальше. Будем считать, что это было вступление
1. Немного о моей игре
Для начала нужна была идея. Мне хотелось что-то достаточно простое, чтобы оценить возможности фреймворка, но и достаточно интересное и оригинальное, чтобы не программировать ради программирования (это здорово, но когда это не единственная цель — это ещё лучше). Я неплохо проектирую интерфейсы, но не умею рисовать, поэтому игра должна была быть простая графически, или вообще текстовая. И тут так уж сложилось, что у меня есть заброшенный сайт с цитатами, с которого я когда-то начинал свой путь в web-разработке (я о нём даже писал на Хабре много лет назад). Поэтому идея возникла такая: игра-викторина «Угадай цитату». В русскоязычном Google Play ничего подобного не было, а в англоязычном была пара поделок низкого качества с сотней скачиваний.
Почти сразу же стало понятно, что просто так отгадывать цитату за цитатой — скучно. Так появились первые «фишки», которые, в итоге, и определили итоговую игру. В первую очередь это были тематические пакеты (то есть пакеты цитат, объединённые одной темой или автором) и баллы (которые начисляются за отгадывание цитат и прохождение пакетов, и тратятся на подсказки и разблокировку новых тем), а также статистика, достижения и избранное.
Первые проблемы начались с первого же экрана…
2. Kivy тормоз или я что-то делаю не так?
Один мой друг любит отвечать на такие вопросы «да» На самом деле, некоторые вещи в Kivy действительно работают медленно, например, создание виджетов. Но это не значит, что это дело нельзя оптимизировать. Об этом я и расскажу.
Так как цитаты и темы хранятся в БД, то, само собой, кнопки с пакетами генерируются динамически. И вот тут-то я обнаружил, что происходит это очень медленно: примерно полсекунды на список из 20 кнопок. Возможно, это и не очень много при загрузке приложения, но при переходе на главный экран из других внутренних экранов приложения — непозволительно много. Здесь стоит отметить, что кнопка к тому моменту уже представляла собой, на самом деле, набор из нескольких элементов, визуально составляющих одну кнопку:
Первым моим побуждением было тем или иным образом закешировать их, и, действительно, опыт показал, что если создать все виджеты заранее, и сохранить их как свойство объекта StartScreen, то всё (кроме первой генерации) работает достаточно быстро. Однако же, данные в кнопках нужно периодически обновлять (хотя бы то же количество отгаданных цитат). Да и загрузку новых пакетов я уже тогда планировал. Конечно, не проблема реализовать и это, но я решил не изобретать велосипед и подумать.
Сначала стоило убедиться, что проблема именно в создании виджетов, поэтому я за несколько минут набросал простенькое приложение на два экрана, в каждом из которых генерировался набор строк из лейбла и чекбокса количеством 50 шт.
Исходный код тестового приложения
main.py:
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock
from time import time
class ListScreen(Screen):
items_box = ObjectProperty(None)
def on_enter(self):
start = time()
for i in range(0,50):
self.items_box.add_widget(ListItem('Item '+str(i)))
self.items_box.bind(minimum_height=self.items_box.setter('height'))
print time()-start
def on_leave(self):
self.items_box.clear_widgets()
class ListItem(BoxLayout):
title = StringProperty('')
def __init__(self, title, **kwargs):
super(ListItem, self).__init__(**kwargs)
self.title = title
class ListApp(App):
sm = ScreenManager()
screens = {}
def build(self):
self.__create_screens()
ListApp.sm.add_widget(ListApp.screens['list1'])
Clock.schedule_interval(self._switch, 1)
return ListApp.sm
def _switch(self, *args):
ListApp.sm.switch_to(ListApp.screens['list1' if ListApp.sm.current != 'list1' else 'list2'])
def __create_screens(self):
ListApp.screens['list1'] = ListScreen(name='list1')
ListApp.screens['list2'] = ListScreen(name='list2')
if __name__ == '__main__':
ListApp().run()
list.kv:
<ListScreen>:
items_box: items_box
BoxLayout:
orientation: "vertical"
AnchorLayout:
size_hint_y: 0.1
padding: self.width*0.1, self.height*0.05
Label:
font_size: root.height*0.05
text: "Some list"
ScrollView:
size_hint_y: 0.9
size: self.size
BoxLayout:
id: items_box
orientation: "vertical"
padding: self.width*0.1, 0
size_hint_y: None
<ListItem>:
orientation: "horizontal"
size_hint_y: None
height: app.sm.height*0.1
Label:
font_size: app.sm.height*0.025
text: root.title
size_hint_x: 0.9
text_size: self.size
valign: "middle"
CheckBox
size_hint_x: 0.1
Запустил на своём стареньком Moto G (gen3) и получил:
11-28 11:44:09.525 1848 2044 I python : 0.5793800354
11-28 11:44:10.853 1848 2044 I python : 0.453143119812
11-28 11:44:12.544 1848 2044 I python : 0.633069992065
11-28 11:44:13.697 1848 2044 I python : 0.369570970535
11-28 11:44:14.988 1848 2044 I python : 0.594089031219
И далее в том же духе. Поиск по этому вопросу ничего не дал, поэтому я обратился к разработчикам. И получил ответ: «Создание виджетов относительно медленное, особенно в зависимости от того, что они содержат. Для создания больших списков лучше использовать RecycleView». Здесь хочу пояснить, почему я вообще описываю этот момент, ведь описание RecycleView есть в документации. Да, действительно, есть, но мало кто способен изучить и запомнить всю документацию перед тем, как начнёт разработку, и найти нужный инструмент бывает непросто, особенно если он нигде не описан в контексте решения конкретной проблемы. Теперь же он описан
Исходный код тестового приложения с RecycleView
main.py:
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.clock import Clock
from time import time
class ListScreen(Screen):
recycle_view = ObjectProperty(None)
items_box = ObjectProperty(None)
def on_enter(self):
start = time()
for i in range(0,50):
self.recycle_view.data.append({'title': 'item'+str(i)})
print time()-start
def on_leave(self):
self.recycle_view.data = []
class ListApp(App):
sm = ScreenManager()
screens = {}
def build(self):
self.__create_screens()
ListApp.sm.add_widget(ListApp.screens['list1'])
Clock.schedule_interval(self._switch, 1)
return ListApp.sm
def _switch(self, *args):
ListApp.sm.switch_to(ListApp.screens['list1' if ListApp.sm.current != 'list1' else 'list2'])
def __create_screens(self):
ListApp.screens['list1'] = ListScreen(name='list1')
ListApp.screens['list2'] = ListScreen(name='list2')
if __name__ == '__main__':
ListApp().run()
list.kv:
<ListScreen>:
recycle_view: recycle_view
items_box: items_box
BoxLayout:
orientation: "vertical"
AnchorLayout:
size_hint_y: 0.1
padding: self.width*0.1, self.height*0.05
Label:
font_size: root.height*0.05
text: "Some list"
RecycleView:
id: recycle_view
size_hint: 1, 0.9
viewclass: "ListItem"
RecycleBoxLayout:
id: items_box
orientation: "vertical"
padding: self.width*0.1, 0
default_size_hint: 1, None
size_hint: 1, None
height: self.minimum_height
<ListItem@BoxLayout>:
orientation: "horizontal"
size_hint: 1, None
height: app.sm.height*0.1
title: ''
Label:
font_size: app.sm.height*0.025
text: root.title
size_hint_x: 0.9
text_size: self.size
valign: "middle"
CheckBox
size_hint_x: 0.1
11-29 13:11:58.196 13121 13203 I python : 0.00388479232788
11-29 13:11:59.192 13121 13203 I python : 0.00648307800293
11-29 13:12:00.189 13121 13203 I python : 0.00288391113281
11-29 13:12:01.189 13121 13203 I python : 0.00324606895447
11-29 13:12:03.188 13121 13203 I python : 0.00265002250671
Более чем в 100 раз быстрее. Впечатляет, не правда ли?
В завершение следует упомянуть, что RecycleView — не панацея. Он не подходит, если размер элемента зависит от содержимого (например, Label, размер которого меняется в зависимости от количества текста).
3. Сервисы. Автозапуск и перезапуск
Следующая проблема, с которой я столкнулся, не поддавалась решению так долго, что я уже малодушно подумывал счесть данный фреймворк непригодным и забить Проблема была с сервисами (в Android так называется процессы, выполняющиеся в фоновом режиме). Создать сервис не так уж и сложно — немного сбивает с толку устаревшая документация, но и только. Однако, в большинстве случаев, много ли толку от сервиса, который, во-первых, не запускается автоматически при загрузке телефона, а во-вторых, не перезапускается, если «выбросить» приложение свайпом из диспетчера задач? По-моему, нет.
На тот момент по этой теме была всего лишь одна статья в официальной wiki, но она, хоть и называлась «Starting Kivy service on bootup», на самом деле всего лишь рассказывала, как при загрузке телефона запустить приложение, но не его сервис (да, такое тоже бывает полезно, но значительно реже, как по мне). Ту статью я, в итоге, переписал, а здесь расскажу подробности.
Допустим, у нас есть примитивный сервис, который всего-то и делает, что периодически выводит в лог строку (этим мы заранее исключаем баги, которые могут возникать из-за особенностей самого сервиса).
from time import sleep
if __name__ == '__main__':
while True:
print "myapp service"
sleep(5)
Из приложения мы запускаем его методом основного класса при помощи PyJnius:
from jnius import autoclass
# ... #
class GuessTheQuoteApp(App):
# ... #
def __start_service(self):
service = autoclass('com.madhattersoft.guessthequote.ServiceGuessthequoteservice')
mActivity = autoclass('org.kivy.android.PythonActivity').mActivity
service.start(mActivity, "")
Если APK собран правильно, при запуске приложения сервис будет стартовать, но этого недостаточно.
Для начала, попробуем сделать так, чтобы он перезапускался при остановке приложения (например, при снятии его из диспетчера задач). Конечно, можно было бы использовать startForeground, но это уже не совсем фоновое выполнение задачи Для него потребуется, как минимум, уведомление — это не всегда подходит. В данном случае идеально подходит флаг START_STICKY, но мы же пишем на Python, что делает задачу не столь тривиальной — по крайней мере, при помощи PyJnius она уже не решается.
Честно говоря, она вообще решается достаточно криво, поскольку я пока не готов становиться одним из разработчиков Python4Android, благодаря которому всё это счастье вообще работает. А изменения нужно вносить именно в код Python4Android. А конкретно, нам нужен файл .buildozer/android/platform/build/dists/guessthequote/src/org/kivy/android/PythonService.java в котором в функции startType() мы меняем флаг START_NOT_STICKY на START_STICKY:
public int startType() {
return START_STICKY;
}
Ура, сервис рестартится. Всё? Конечно, нет Потому что он тут же валится с ошибкой:
E AndroidRuntime: Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.os.Bundle android.content.Intent.getExtras()' on a null object reference
Проблема в функции onStartCommand(Intent intent, int flags, int startId), поскольку после перезапуска intent у нас null. Что ж, перепишем и её:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (pythonThread != null) {
Log.v("python service", "service exists, do not start again");
return START_NOT_STICKY;
}
if (intent != null) {
startIntent = intent;
Bundle extras = intent.getExtras();
androidPrivate = extras.getString("androidPrivate");
androidArgument = extras.getString("androidArgument");
serviceEntrypoint = extras.getString("serviceEntrypoint");
pythonName = extras.getString("pythonName");
pythonHome = extras.getString("pythonHome");
pythonPath = extras.getString("pythonPath");
pythonServiceArgument = extras.getString("pythonServiceArgument");
pythonThread = new Thread(this);
pythonThread.start();
if (canDisplayNotification()) {
doStartForeground(extras);
}
} else {
pythonThread = new Thread(this);
pythonThread.start();
}
return startType();
}
Увы:
F DEBUG : Abort message: 'art/runtime/java_vm_ext.cc:410] JNI DETECTED ERROR IN APPLICATION: GetStringUTFChars received NULL jstring'
Проблема в том, что функция nativeStart() не получает нужных Extras. К сожалению, два из них мне пришлось захардкодить. В итоге выглядит это так:
@Override
public void run(){
String package_root = getFilesDir().getAbsolutePath();
String app_root = package_root + "/app";
File app_root_file = new File(app_root);
PythonUtil.loadLibraries(app_root_file);
this.mService = this;
if (androidPrivate == null) {
androidPrivate = package_root;
}
if (androidArgument == null) {
androidArgument = app_root;
}
if (serviceEntrypoint == null) {
serviceEntrypoint = "./service/main.py"; // hardcoded
}
if (pythonName == null) {
pythonName = "guessthequoteservice"; // hardcoded
}
if (pythonHome == null) {
pythonHome = app_root;
}
if (pythonPath == null) {
pythonPath = package_root;
}
if (pythonServiceArgument == null) {
pythonServiceArgument = app_root+":"+app_root+"/lib";
}
nativeStart(
androidPrivate, androidArgument,
serviceEntrypoint, pythonName,
pythonHome, pythonPath,
pythonServiceArgument);
stopSelf();
}
Теперь всё.
Перейдём к автозапуску сервиса при запуске телефона. После предыдущей проблемы это будет уже проще. (На самом деле же всё было наоборот — я очень долго не мог понять, что именно нужно добавить, поскольку информации об этом не было вообще нигде, и сами разработчики тоже не знали, как решить данную задачу. И только разобравшись параллельно с вопросом перезапуска, я понял, что нужно сделать.)
Для начала понадобится разрешение RECEIVE_BOOT_COMPLETED — это просто. А затем — BroadcastReceiver, его придётся добавить в AndroidManifest вручную, но это тоже не проблема. Проблема в том, что в нём писать
Решение для запуска приложения (не сервиса) выглядит так:
package com.madhattersoft.guessthequote;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.Context;
import org.kivy.android.PythonActivity;
public class MyBroadcastReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
Intent ix = new Intent(context, PythonActivity.class);
ix.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(ix);
}
}
Сначала я попытался просто переписать его для сервиса:
package com.madhattersoft.guessthequote;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.Context;
import com.madhattersoft.guessthequote.ServiceGuessthequoteservice;
public class MyBroadcastReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
Intent ix = new Intent(context, ServiceGuessthequoteservice.class);
ix.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startService(ix);
}
}
Ага, разогнался
E AndroidRuntime: java.lang.RuntimeException: Unable to start service com.madhattersoft.guessthequote.ServiceGuessthequoteservice@8c96929 with Intent { cmp=com.madhattersoft.guessthequote/.ServiceGuessthequoteservice }: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.os.Bundle.getString(java.lang.String)' on a null object reference
Думаю, вам уже понятно, что проблема в тех самых Extras. Мне же тогда об этом было узнать неоткуда. Но не буду тянуть, рабочий код выглядит так:
package import com.madhattersoft.guessthequote;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.Context;
import com.madhattersoft.guessthequote.ServiceGuessthequoteservice;
public class MyBroadcastReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
String package_root = context.getFilesDir().getAbsolutePath();
String app_root = package_root + "/app";
Intent ix = new Intent(context, ServiceGuessthequoteservice.class);
ix.putExtra("androidPrivate", package_root);
ix.putExtra("androidArgument", app_root);
ix.putExtra("serviceEntrypoint", "./service/main.py");
ix.putExtra("pythonName", "guessthequoteservice");
ix.putExtra("pythonHome", app_root);
ix.putExtra("pythonPath", package_root);
ix.putExtra("pythonServiceArgument", app_root+":"+app_root+"/lib");
ix.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startService(ix);
}
}
Фух
Локализация и мультиязычность
В целом, для локализации можно использовать gettext, или же поступить ещё проще — создать папку lang, в ней по файлу на каждый язык (например, en.py и ru.py), определить там все слова и фразы в виде переменных/констант, и далее подключить нужный модуль. Примерно так:
if autoclass('java.util.Locale').getDefault().getLanguage() in ('ru', 'uk', 'be'):
import lang.ru as lang
else:
import lang.en as lang
GuessTheQuote.lang = lang
Статическая переменная использована для того, чтобы языковые константы было удобно использовать в kv-файле:
app.lang.some_phrase
Это, в общем-то, довольно тривиально, а основное, о чём я хотел рассказать в аспекте локализации — как задать константы в res/values/strings.xml и отдельных локализациях. Зачем это нужно? Как минимум, чтобы задать название приложения на разных языках, а также чтобы прописать такие константы, как app_id для сервисов Google Play и facebook_app_id для сервисов Facebook.
По-умолчанию P4A генерирует strings.xml следующего содержания:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Guess The Quote</string>
<string name="private_version">1517538478.81</string>
<string name="presplash_color">#2EBCB2</string>
<string name="urlScheme">kivy</string>
</resources>
При этом название приложения и цвет фона экрана загрузки можно задать в buildozer.spec. На первый взгляд, этого достаточно, но это только в том случае, если приложение одноязычное, и дополнительные строковые константы не нужны, а это как-то минималистично Конечно, никто не запрещает вручную прописать всё необходимое, но при следующей сборке оно затрётся. Также можно вручную создать папки с локализациями, например, values-ru, но они при новых сборках они не будут обновляться. Поэтому лучше ещё раз подправить P4A, а именно, файл .buildozer/android/platform/build/dists/guessthequote/build.py следующим образом:
# оригинальный код, в текущей версии P4A начинается на строке 370
render(
'strings.tmpl.xml',
'res/values/strings.xml',
args=args,
url_scheme=url_scheme,
)
# заменяем на усовершенствованный :)
local_args = {'be': argparse.Namespace(**vars(args)), 'ru': argparse.Namespace(**vars(args)), 'uk': argparse.Namespace(**vars(args))}
for key in local_args:
local_args[key].name = u'Угадай цитату!' # ну захардкодил, да, ну не готов я пока сделать свой форк P4A и buildozer, чтобы сделать это через передачу параметра
for i in os.listdir('res'):
if i[:6] == 'values':
render(
'strings.tmpl.xml',
'res/'+i+'/strings.xml',
args=(args if i == 'values' else local_args[i[7:10]]),
url_scheme=url_scheme,
)
# и ещё один фрагмент, в текущей версии P4A начиная со строки 388
with open(join(dirname(__file__), 'res',
'values', 'strings.xml')) as fileh:
lines = fileh.read()
with open(join(dirname(__file__), 'res',
'values', 'strings.xml'), 'w') as fileh:
fileh.write(re.sub(r'"private_version">[0-9.]*<',
'"private_version">{}<'.format(
str(time.time())), lines))
# тоже заменяем на аналогичный в цикле
for i in os.listdir('res'):
if i[:6] == 'values':
with open(join(dirname(__file__), 'res',
i, 'strings.xml')) as fileh:
lines = fileh.read()
with open(join(dirname(__file__), 'res',
i, 'strings.xml'), 'w') as fileh:
fileh.write(re.sub(r'"private_version">[0-9.]*<',
'"private_version">{}<'.format(
str(time.time())), lines))
Ну а все необходимые вам строковые константы нужно прописать в файле .buildozer/android/platform/build/dists/guessthequote/templates/strings.tmpl.xml
Продолжение следует
Если статья покажется сообществу интересной, во второй части я опишу самые интересные вещи: покупки в приложении, интеграцию сервисов Google Play Games и Facebook SDK, и подготовку release version с последующей публикацией в Google Play, а также подготовлю проект на Github с модулями для реализации описанных задач. Если вам интересны ещё какие-то подробности — напишите в комментариях, постараюсь по возможности осветить.
Язык программирования Python занимает первое место в списке языков программирования. Одна из многих причин — отличная поддержка библиотек для создания приложений мирового класса. Одной из таких библиотек является Kivy на Python, которая является кроссплатформенной библиотекой и используется для создания приложений с поддержкой multi-touch. Мы подробно узнаем о различных аспектах в этом руководстве по Kivy, в этой статье рассматриваются следующие темы:
- Что такое Киви?
- Киви Архитектура
- Создание простого приложения с использованием Python Kivy
- Виджеты Kivy
- Еще несколько взаимодействий с виджетами
- Что такое язык киви?
- Python и язык киви
- Киви Недвижимость
- Анимации
- Панель настроек Kivy
- Создание Android APK
Что такое Киви?
Kivy — это кроссплатформенная бесплатная библиотека Python с открытым исходным кодом для создания мультитач-приложений с естественным пользовательским интерфейсом. Kivy работает на поддерживаемых платформах, таких как Windows, OS X, Linux, Raspberry Pi, Android и т. Д.
Он распространяется под лицензией MIT и полностью бесплатен для использования. Фреймворк kivy стабилен и имеет хорошо документированный API.
Графический движок построен на OpenGL ES2 с использованием быстрого и современного конвейера. В набор инструментов входит более 20 виджетов, и все они легко расширяемы.
Киви Архитектура
Архитектура Киви состоит из следующего:
- Основные поставщики и поставщики ввода
- Графика
- Основной
- UIX
- Модули
- Входные события
- Виджеты и диспетчеризация ввода
Давайте посмотрим на простое приложение, использующее Python kivy и несколько основных виджетов, таких как label и FloatLayout.
Создание простого приложения с использованием Python Kivy
В этом приложении метка будет перемещаться с помощью мультитач, и вы даже можете изменить размер метки.
from kivy.app import App from kivy.uix.scatter import Scatter from kivy.uix.label import Label from kivy.uix.floatlayout import FloatLayout class SimpleApp(App): def build(self): f = FloatLayout() s = Scatter() l = Label(text="Edureka!", font_size=150) f.add_widget(s) s.add_widget(l) return f if __name__ == "__main__": SimpleApp().run()
Вывод:
Kivy Widgets
Давайте посмотрим на различные виджеты kivy. Виджеты kivy можно разделить на следующие категории.
- Виджеты UX
- Макеты
- Сложные UX-виджеты
- Виджеты поведения
- Диспетчер экрана
Виджеты UX
- Этикетка
- Кнопка
- Флажок
- Изображение
- Слайдер
- Индикатор
- Ввод текста
- Кнопка-переключатель
- Выключатель
- видео
Ярлык
Виджет метки используется для визуализации текста. Он поддерживает строки как ascii, так и unicode. Вот простой пример, показывающий, как мы можем использовать виджет метки в нашем приложении.
from kivy.app import App from kivy.uix.label import Label class SimpleApp(App): def build(self): l = Label(text="Edureka!",font_size=150) return l if __name__ == "__main__": SimpleApp().run()
Вывод:
Кнопка
Кнопка — это метка с действиями, которые запускаются при нажатии кнопки. Для настройки кнопки используются те же параметры, что и для метки. Вот простой пример, показывающий виджет кнопки. Он меняет состояние при нажатии, и мы даже можем добавить свойства или привязать некоторые действия к кнопке.
from kivy.app import App from kivy.uix.button import Button class SimpleApp(App): def build(self): def a(instance,value): print("welcome to edureka") btn = Button(text="Edureka!",font_size=150) btn.bind(state=a) return btn if __name__ == "__main__": SimpleApp().run()
Вывод:
Флажок
Флажок — это кнопка с двумя состояниями, которую можно установить или снять. Вот небольшой пример, показывающий, как мы можем использовать флажок в приложении kivy.
from kivy.app import App from kivy.uix.checkbox import CheckBox class SimpleApp(App): def build(self): def on_checkbox_active(checkbox, value): if value: print('The checkbox', checkbox, 'is active') else: print('The checkbox', checkbox, 'is inactive') checkbox = CheckBox() checkbox.bind(active=on_checkbox_active) return checkbox if __name__ == "__main__": SimpleApp().run()
Вывод:
Изображение
Этот виджет используется для отображения изображения. Когда вы запустите эту программу, она покажет изображение в приложении.
from kivy.app import App from kivy.uix.image import Image class SimpleApp(App): def build(self): img = Image(source="logo.png") return img if __name__ == "__main__": SimpleApp().run()
Вывод:
Ползунок
Виджет ползунка поддерживает горизонтальную и вертикальную ориентацию и используется в качестве полосы прокрутки. Вот простой пример, показывающий слайдер в приложении kivy.
from kivy.app import App from kivy.uix.slider import Slider class SimpleApp(App): def build(self): slide = Slider(orientation='vertical', value_track=True, value_track_color=(1,0,0,1)) return slide if __name__ == "__main__": SimpleApp().run()
Вывод:
Индикатор выполнения
Он используется для отслеживания прогресса любой задачи. Вот простой пример, показывающий, как мы используем индикатор выполнения в приложении kivy.
from kivy.app import App from kivy.uix.progressbar import ProgressBar class SimpleApp(App): def build(self): Progress = ProgressBar(max=1000) Progress.value = 650 return Progress if __name__ == "__main__": SimpleApp().run()
Вывод:
Ввод текста
Он предоставляет поле для редактирования простого текста.
from kivy.app import App from kivy.uix.textinput import TextInput class SimpleApp(App): def build(self): t = TextInput(font_size=150) return t if __name__ == "__main__": SimpleApp().run()
Вывод:
Кнопка-переключатель
Он действует как флажок, когда вы касаетесь или щелкаете его, состояние переключается. Вот пример, чтобы показать кнопку-переключатель в приложении kivy. Когда вы нажимаете на переключатель, он меняет состояние с «нормального» на «вниз».
from kivy.app import App from kivy.uix.togglebutton import ToggleButton from kivy.uix.floatlayout import FloatLayout class SimpleApp(App): def build(self): b = ToggleButton(text="python", border=(26,26,26,26), font_size=200) return b if __name__ == "__main__": SimpleApp().run()
Вывод:
Переключить
Это похоже на механический переключатель, который включается или выключается. Вот простой пример, показывающий, как он используется в приложении kivy.
from kivy.app import App from kivy.uix.switch import Switch class SimpleApp(App): def build(self): s = Switch(active=True) return s if __name__ == "__main__": SimpleApp().run()
Вывод:
Видео
Он используется для отображения видео файлов или потоков. Вот простой пример, демонстрирующий, как это работает в приложении kivy.
from kivy.app import App from kivy.uix.video import Video class SimpleApp(App): def build(self): s = Video(source="abc.mp4", play=True) return s if __name__ == "__main__": SimpleApp().run()
Вывод: будет воспроизведено видео, указанное в ссылке на файл.
Макеты
Виджет макета не выполняет рендеринг, а просто действует как триггер, который определенным образом упорядочивает свои дочерние элементы.
- Макет якоря
- Макет коробки
- Макет поплавка
- Макет сетки
- Макет страницы
- Относительный макет
- Макет разброса
- Макет стека
Макет якоря
Он выравнивает дочерние виджеты по границе (слева, справа, вверх, вниз) или по центру. Вот простой пример, показывающий, как макет привязки используется в приложении kivy, когда привязка установлена в центральное положение, мы можем установить ее в разные положения, такие как нижний левый, снизу вверх и т. Д.
from kivy.app import App from kivy.uix.button import Button from kivy.uix.anchorlayout import AnchorLayout class SimpleApp(App): def build(self): layout = AnchorLayout( anchor_x='center', anchor_y='center') btn = Button(text='Hello World') layout.add_widget(btn) return layout if __name__ == "__main__": SimpleApp().run()
Вывод:
Макет коробки
Он размещает дочерние виджеты в горизонтальных или вертикальных полях. В этом примере макет блока хранит виджеты в двух блоках, как показано ниже.
from kivy.app import App from kivy.uix.button import Button from kivy.uix.boxlayout import BoxLayout class SimpleApp(App): def build(self): layout = BoxLayout(orientation='vertical') btn = Button(text='Hello World') btn1 = Button(text="Welcome to edureka") layout.add_widget(btn) layout.add_widget((btn1)) return layout if __name__ == "__main__": SimpleApp().run()
Вывод:
Плавающий макет
Он учитывает свойства size_hint и pos_hint своих дочерних виджетов.
from kivy.app import App from kivy.uix.scatter import Scatter from kivy.uix.label import Label from kivy.uix.floatlayout import FloatLayout class SimpleApp(App): def build(self): f = FloatLayout() s = Scatter() l = Label(text="Edureka!", font_size=150) f.add_widget(s) s.add_widget(l) return f if __name__ == "__main__": SimpleApp().run()
Вывод:
Макет сетки
Он помещает дочерние виджеты в коробку.
from kivy.app import App from kivy.uix.button import Button from kivy.uix.gridlayout import GridLayout class SimpleApp(App): def build(self): layout = GridLayout(cols=2) layout.add_widget(Button(text='hello')) layout.add_widget(Button(text='world')) layout.add_widget(Button(text='welcome to')) layout.add_widget(Button(text='edureka')) return layout if __name__ == "__main__": SimpleApp().run()
Вывод:
Макет страницы
Он используется для создания многостраничного макета.
from kivy.app import App from kivy.uix.button import Button from kivy.uix.pagelayout import PageLayout class SimpleApp(App): def build(self): layout = PageLayout() layout.add_widget(Button(text='hello',background_color=(1,0,0,1))) layout.add_widget(Button(text='world',background_color=(0,1,0,1))) layout.add_widget(Button(text='welcome to',background_color=(1,1,1,1))) layout.add_widget(Button(text='edureka',background_color=(0,1,1,1))) return layout if __name__ == "__main__": SimpleApp().run()
Вывод:
Относительный макет
Он позволяет вам устанавливать относительные координаты для дочерних виджетов.
from kivy.app import App from kivy.uix.relativelayout import RelativeLayout from kivy.uix.boxlayout import BoxLayout from kivy.uix.button import Button from kivy.uix.label import Label from kivy.lang import Builder res = Builder.load_string('''BoxLayout: Label: text: 'Left' Button: text: 'Middle' on_touch_down: print('Middle: {}'.format(args[1].pos)) RelativeLayout: on_touch_down: print('Relative: {}'.format(args[1].pos)) Button: text: 'Right' on_touch_down: print('Right: {}'.format(args[1].pos))''') class SimpleApp(App): def build(self): return res if __name__ == "__main__": SimpleApp().run()
В этой программе мы использовали подход на языке KV, он будет рассмотрен позже на этом занятии.
Вывод:
Точечный макет
Он реализован в виде плавающего макета внутри скаттера. Вы можете изменить положение виджетов, используя разброс.
from kivy.app import App from kivy.uix.scatterlayout import ScatterLayout from kivy.uix.label import Label class SimpleApp(App): def build(self): s = ScatterLayout() l = Label(text='edureka') s.add_widget(l) return s if __name__ == "__main__": SimpleApp().run()
Вывод:
Макет стека
Он размещает виджеты по горизонтали или вертикали и столько, сколько может поместиться.
from kivy.app import App from kivy.uix.stacklayout import StackLayout from kivy.uix.button import Button class SimpleApp(App): def build(self): root = StackLayout() for i in range(25): btn = Button(text=str(i), width=100 + i * 5, size_hint=(None, 0.15)) root.add_widget(btn) return root if __name__ == "__main__": SimpleApp().run()
Вывод:
Теперь, когда мы закончили с макетами, давайте взглянем на виджеты поведения в Kivy.
Виджеты поведения
Эти виджеты не выполняют рендеринга, а действуют в соответствии с графическими инструкциями или взаимодействием (касанием) своих дочерних элементов.
- Разброс
- Просмотр трафарета
Разброс
Scatter используется для создания интерактивных виджетов, которые можно вращать и масштабировать двумя или более пальцами в системе мультитач.
from kivy.app import App from kivy.uix.scatter import Scatter from kivy.uix.image import Image class SimpleApp(App): def build(self): s = Scatter() s.add_widget(Image(source="logo.png")) return s if __name__ == "__main__": SimpleApp().run()
Вывод:
Просмотр по шаблону
Представление трафарета ограничивает рисование дочерних виджетов ограничивающей рамкой вида трафарета. В этом примере мы используем простой виджет метки. Вид трафарета лучше всего использовать, когда мы рисуем на холсте, и он ограничивает действия ограниченной областью в приложении, а не всем окном.
from kivy.app import App from kivy.uix.stencilview import StencilView from kivy.uix.label import Label from kivy.uix.scatter import Scatter class SimpleApp(App): def build(self): s = StencilView() sc = Scatter() s.add_widget(sc) sc.add_widget(Label(text='edureka')) return s if __name__ == "__main__": SimpleApp().run()
Вывод:
Диспетчер экрана
Это виджет, который используется для управления несколькими экранами вашего приложения. Он использует переходную базу для переключения с одного экрана на другой.
from kivy.app import App from kivy.base import runTouchApp from kivy.lang import Builder from kivy.properties import ListProperty from kivy.uix.boxlayout import BoxLayout from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition import time import random class FirstScreen(Screen): pass class SecondScreen(Screen): pass class ColourScreen(Screen): colour = ListProperty([1., 0., 0., 1.]) class MyScreenManager(ScreenManager): def new_colour_screen(self): name = str(time.time()) s = ColourScreen(name=name, colour=[random.random() for _ in range(3)] + [1]) self.add_widget(s) self.current = name root_widget = Builder.load_string(''' #:import FadeTransition kivy.uix.screenmanager.FadeTransition MyScreenManager: transition: FadeTransition() FirstScreen: SecondScreen: <FirstScreen>: name: 'first' BoxLayout: orientation: 'vertical' Label: text: 'first screen!' font_size: 30 Image: source: 'logo.png' allow_stretch: False keep_ratio: False BoxLayout: Button: text: 'goto second screen' font_size: 30 on_release: app.root.current = 'second' Button: text: 'get random colour screen' font_size: 30 on_release: app.root.new_colour_screen() <SecondScreen>: name: 'second' BoxLayout: orientation: 'vertical' Label: text: 'second screen!' font_size: 30 Image: source: 'logo1.jpg' allow_stretch: False keep_ratio: False BoxLayout: Button: text: 'goto first screen' font_size: 30 on_release: app.root.current = 'first' Button: text: 'get random colour screen' font_size: 30 on_release: app.root.new_colour_screen() <ColourScreen>: BoxLayout: orientation: 'vertical' Label: text: 'colour {:.2},{:.2},{:.2} screen'.format(*root.colour[:3]) font_size: 30 Widget: canvas: Color: rgba: root.colour Ellipse: pos: self.pos size: self.size BoxLayout: Button: text: 'goto first screen' font_size: 30 on_release: app.root.current = 'first' Button: text: 'get random colour screen' font_size: 30 on_release: app.root.new_colour_screen() ''') class ScreenManagerApp(App): def build(self): return root_widget ScreenManagerApp().run()
Вывод:
Еще несколько взаимодействий с виджетами
Давайте посмотрим на довольно интересный пример, где мы собираемся связать взаимодействие двух виджетов вместе с помощью метода bind.
from kivy.app import App from kivy.uix.scatter import Scatter from kivy.uix.label import Label from kivy.uix.floatlayout import FloatLayout from kivy.uix.boxlayout import BoxLayout from kivy.uix.textinput import TextInput class SimpleApp(App): def build(self): b = BoxLayout(orientation="vertical") t = TextInput(font_size=100,text="default",size_hint_y=None, height=100) f = FloatLayout() s = Scatter() l = Label(text="default", font_size=150) t.bind(text=l.setter("text")) f.add_widget(s) s.add_widget(l) b.add_widget(t) b.add_widget(f) return b if __name__ == "__main__": SimpleApp().run()
Что такое язык киви?
По мере того, как наше приложение становится все более сложным, становится трудно поддерживать конструкцию виджетов и явное объявление привязок. Чтобы преодолеть эти недостатки, альтернативой является язык kv, также известный как язык kivy или kvlang.
Язык kv позволяет создавать дерево виджетов декларативным образом, он позволяет создавать очень быстрые прототипы и гибко вносить изменения в пользовательский интерфейс. Это также помогает, отделяя логику приложения от пользовательского интерфейса.
Как загрузить файл KV?
Есть два способа загрузить файл kv в свое приложение.
- Соглашение по названию — kivy ищет файл с тем же именем, что и ваше приложение, начиная со строчной буквы минус «приложение», если он присутствует в имени вашего приложения.
SimpleApp -
simple.kv
Если это определяет корневой виджет, он будет добавлен в дерево виджетов в качестве основы приложения.
2. Builder — вы можете напрямую указать kivy загрузить файл kv с помощью Builder.
Builder.load_file("filename.kv") #or Builder.load_string(''' ''') #you can directly put your kv file as string using this approach.
Языковые правила KV
Корень объявляется путем объявления класса вашего корневого виджета
Widget:
Правило класса, объявленное именем класса виджета между ‹›, определяет внешний вид и поведение экземпляра этого класса
<Widget>:
Для языка KV есть три конкретных ключевых слова.
- приложение: относится к экземпляру приложения.
- root: относится к базовому виджету или корневому виджету.
- self: относится к текущему виджету.
Давайте рассмотрим простой пример, чтобы понять, как мы используем язык KV в нашем приложении.
from kivy.app import App from kivy.uix.scatter import Scatter from kivy.uix.label import Label from kivy.uix.floatlayout import FloatLayout from kivy.uix.textinput import TextInput from kivy.uix.boxlayout import BoxLayout class ScatterTextWidget(BoxLayout): pass class SimpleApp(App): def build(self): return ScatterTextWidget() if __name__ == "__main__": SimpleApp().run()
Файл .KV
<ScatterTextWidget>: orientation: 'vertical' TextInput: id: my_textinput font_size: 150 size_hint_y: None height: 200 text: 'default' FloatLayout: Scatter: Label: text: my_textinput.text font_size: 150
Вывод:
Python и язык киви
Язык Python и kivy значительно упрощает любому разработчику написание читаемого кода для любого приложения, а также упрощает определение свойств и привязок для различных виджетов.
Давайте попробуем смешать языки Python и Kivy в следующем примере.
from kivy.app import App from kivy.uix.scatter import Scatter from kivy.uix.label import Label from kivy.uix.floatlayout import FloatLayout from kivy.uix.textinput import TextInput from kivy.uix.boxlayout import BoxLayout import random class Text(BoxLayout): def change_label_colour(self, *args): colour = [random.random() for i in range(3)] + [1] label = self.ids['my_label'] label.color = colour class SimpleApp(App): def build(self): return Text() if __name__ == "__main__": SimpleApp().run()
Файл .KV
#:import color random <Text>: orientation: 'vertical' TextInput: id: my_textinput font_size: 150 size_hint_y: None height: 200 text: 'default' on_text: my_label.color = [color.random() for i in range(3)] + [1] FloatLayout: Scatter: Label: id: my_label text: my_textinput.text font_size: 150
Вывод:
Киви Недвижимость
Свойства — это более простой способ определять события и связывать их вместе. Существуют различные типы свойств для описания типа данных, которые вы хотите обрабатывать.
- StringProperty
- NumericProperty
- BoundedNumericProperty
- ObjectProperty
- DictProperty
- ListProperty
- OptionProperty
- Псевдоним
- BooleanProperty
- ReferenceListProperty
Как декларировать недвижимость?
Мы должны объявить свойства на уровне класса. Вот простой пример, показывающий, как мы можем использовать свойства в приложении.
from kivy.app import App from kivy.uix.widget import Widget from kivy.uix.button import Button from kivy.uix.boxlayout import BoxLayout from kivy.properties import ListProperty class RootWidget(BoxLayout): def __init__(self, **kwargs): super(RootWidget, self).__init__(**kwargs) self.add_widget(Button(text='btn 1')) cb = CustomBtn() cb.bind(pressed=self.btn_pressed) self.add_widget(cb) self.add_widget(Button(text='btn 2')) def btn_pressed(self, instance, pos): print('pos: printed from root widget: {pos}'.format(pos=pos)) class CustomBtn(Widget): pressed = ListProperty([0, 0]) def on_touch_down(self, touch): if self.collide_point(*touch.pos): self.pressed = touch.pos # we consumed the touch. return False here to propagate # the touch further to the children. return True return super(CustomBtn, self).on_touch_down(touch) def on_pressed(self, instance, pos): print('pressed at {pos}'.format(pos=pos)) class TestApp(App): def build(self): return RootWidget() if __name__ == '__main__': TestApp().run()
Вывод:
Наш CustomBtn не имеет визуального представления и поэтому выглядит черным. Вы можете коснуться / щелкнуть черную область, чтобы увидеть результат на консоли.
Анимации
Мы можем добавлять анимацию в kivy-приложение, используя animation или animationTransition для анимации свойств виджета. В этом примере прямоугольник перемещается в случайное место при каждом щелчке по прямоугольнику.
from kivy.base import runTouchApp from kivy.lang import Builder from kivy.uix.widget import Widget from kivy.animation import Animation from kivy.core.window import Window from random import random Builder.load_string(''' <Root>: ARect: pos: 500, 300 <ARect>: canvas: Color: rgba: 0, 0, 1, 1 Rectangle: pos: self.pos size: self.size ''') class Root(Widget): pass class ARect(Widget): def circle_pos(self): Animation.cancel_all(self) random_x = random() * (Window.width - self.width) random_y = random() * (Window.height - self.height) anim = Animation(x=random_x, y=random_y, duration=4, t='out_elastic') anim.start(self) def on_touch_down(self, touch): if self.collide_point(*touch.pos): self.circle_pos() runTouchApp(Root())
Вывод:
Панель настроек Kivy
Панель настроек в kivy в основном предоставляет различные параметры, которые мы можем выбрать для настройки приложения. В следующем примере показана кнопка, открывающая панель настроек после освобождения.
from kivy.app import App from kivy.lang import Builder from kivy.uix.boxlayout import BoxLayout Builder.load_string(''' <Interface>: orientation: 'vertical' Button: text: 'Settings' font_size: 100 on_release: app.open_settings() ''') class Interface(BoxLayout): pass class SettingsApp(App): def build(self): return Interface() SettingsApp().run()
Вывод:
Создание Android APK
Мы можем использовать инструмент Buildozer, чтобы сделать автономный полнофункциональный APK для Android. В первую очередь следует позаботиться о зависимостях после установки инструмента. Если вы используете kivy в Windows, может быть несколько избыточностей, поэтому лучше использовать Linux или любую другую платформу. Вместо этого вы также можете использовать виртуальную коробку, чтобы сделать APK и для Windows.
Ниже приведены шаги, которые необходимо выполнить, чтобы создать автономный APK-файл для Android вашего приложения kivy.
- Первым шагом после установки является создание файла .spec с помощью buildozer. Этот файл будет содержать все параметры, которые вам понадобятся при создании вашего приложения. Следующая команда создаст файл .spec со значениями по умолчанию.
buildozer init
2. После создания файла .spec вам необходимо внести несколько изменений, например заголовок, имя пакета, ориентацию, версию, требования и т. Д.
3. Следующим шагом после внесения всех необходимых изменений в файл .spec является сборка APK. Следующая команда переведет APK-файл Android в режим сборки.
buildozer android debug
4. Последний аргумент «deploy» указывает buildozer на автоматическую установку APK на ваше устройство после завершения процесса сборки.
buildozer android debug deploy
На этом мы подошли к концу статьи, где мы узнали, как создавать приложения с поддержкой multi-touch, используя библиотеку kivy python. Я надеюсь, что вы понимаете все, о чем вам рассказали в этом уроке.
Если вы хотите ознакомиться с другими статьями о самых популярных технологиях на рынке, таких как искусственный интеллект, DevOps, этический взлом, посетите официальный сайт Edureka.
Обязательно обратите внимание на другие статьи в этой серии, которые объяснят различные другие аспекты Python и Data Science.
1. Классификатор машинного обучения на Python
2. Шпаргалка по Python Scikit-Learn
3. Инструменты машинного обучения
4. Библиотеки Python для науки о данных и машинного обучения
5. Чат-бот на Python
6. Коллекции Python
7. Модули Python
8. Навыки разработчика Python
9. Вопросы и ответы на собеседовании ООП
10. Резюме для Python-разработчика
11. Исследовательский анализ данных в Python
12. Змейка с модулем Python Turtle
13. Зарплата разработчика Python
14. Анализ главных компонентов
15. Python vs C ++
16. Учебник по царапинам
17. Python SciPy
18. Метод регрессии наименьших квадратов
19. Шпаргалка по Jupyter Notebook
20. Основы Python
21. Программы-шаблоны Python
22. Генераторы в Python
23. Python Decorator
24. Python Spyder IDE
25. Что такое программирование сокетов в Python
26. 10 лучших книг для изучения и практики Python
27. Робот-фреймворк с Python
28. Змейка на Python с использованием PyGame
29. Интервью с Django: вопросы и ответы
30. 10 лучших приложений Python
31. Хеш-таблицы и хэш-карты в Python
32. Python 3.8
33. Машина опорных векторов
34. Учебник по Python
Первоначально опубликовано на https://www.edureka.co 14 октября 2019 г.
Getting Started
Getting up and running on python-for-android (p4a) is a simple process
and should only take you a couple of minutes. We’ll refer to Python
for android as p4a in this documentation.
Concepts
Basic:
- requirements: For p4a, all your app’s dependencies must be specified
via--requirements
similar to the standard requirements.txt.
(Unless you specify them via a setup.py/install_requires)
All dependencies will be mapped to «recipes» if any exist, so that
many common libraries will just work. See «recipe» below for details. - distribution: A distribution is the final «build» of your
compiled project + requirements, as an Android project assembled by
p4a that can be turned directly into an APK. p4a can contain multiple
distributions with different sets of requirements. - build: A build refers to a compiled recipe or distribution.
- bootstrap: A bootstrap is the app backend that will start your
application. The default for graphical applications is SDL2.
You can also use e.g. the webview for web apps, or service_only/service_library for
background services. Different bootstraps have different additional
build options.
Advanced:
- recipe:
A recipe is a file telling p4a how to install a requirement
that isn’t by default fully Android compatible.
This is often necessary for Cython or C/C++-using python extensions.
p4a has recipes for many common libraries already included, and any
dependency you specified will be automatically mapped to its recipe.
If a dependency doesn’t work and has no recipe included in p4a,
then it may need one to work.
Installation
Installing p4a
p4a is now available on Pypi, so you can install it using pip:
pip install python-for-android
You can also test the master branch from Github using:
pip install git+https://github.com/kivy/python-for-android.git
Installing Dependencies
p4a has several dependencies that must be installed:
- ant
- autoconf (for libffi and other recipes)
- automake
- ccache (optional)
- cmake (required for some native code recipes like jpeg’s recipe)
- cython (can be installed via pip)
- gcc
- git
- libncurses (including 32 bit)
- libtool (for libffi and recipes)
- libssl-dev (for TLS/SSL support on hostpython3 and recipe)
- openjdk-8
- patch
- python3
- unzip
- virtualenv (can be installed via pip)
- zlib (including 32 bit)
- zip
On recent versions of Ubuntu and its derivatives you may be able to
install most of these with:
sudo dpkg --add-architecture i386 sudo apt-get update sudo apt-get install -y build-essential ccache git zlib1g-dev python3 python3-dev libncurses5:i386 libstdc++6:i386 zlib1g:i386 openjdk-8-jdk unzip ant ccache autoconf libtool libssl-dev
On Arch Linux you should be able to run the following to
install most of the dependencies (note: this list may not be
complete):
sudo pacman -S core/autoconf core/automake core/gcc core/make core/patch core/pkgconf extra/cmake extra/jdk8-openjdk extra/python-pip extra/unzip extra/zip
On macOS:
brew install autoconf automake libtool openssl pkg-config brew tap homebrew/cask-versions brew install --cask homebrew/cask-versions/adoptopenjdk8
Installing Android SDK
Warning
python-for-android is often picky about the SDK/NDK versions.
Pick the recommended ones from below to avoid problems.
Basic SDK install
You need to download and unpack the Android SDK and NDK to a directory (let’s say $HOME/Documents/):
- Android SDK
- Android NDK
For the Android SDK, you can download ‘just the command line
tools’. When you have extracted these you’ll see only a directory
named tools
, and you will need to run extra commands to install
the SDK packages needed.
For Android NDK, note that modern releases will only work on a 64-bit
operating system. The minimal, and recommended, NDK version to use is r25b:
- Go to ndk downloads page
- Windows users should create a virtual machine with an GNU Linux os
installed, and then you can follow the described instructions from within
your virtual machine.
Platform and build tools
First, install an API platform to target. The recommended *target* API
level is 27, you can replace it with a different number but
keep in mind other API versions are less well-tested and older devices
are still supported down to the recommended specified *minimum*
API/NDK API level 21:
$SDK_DIR/tools/bin/sdkmanager "platforms;android-27"
Second, install the build-tools. You can use
$SDK_DIR/tools/bin/sdkmanager --list
to see all the
possibilities, but 28.0.2 is the latest version at the time of writing:
$SDK_DIR/tools/bin/sdkmanager "build-tools;28.0.2"
Configure p4a to use your SDK/NDK
Then, you can edit your ~/.bashrc
or other favorite shell to include new environment
variables necessary for building on android:
# Adjust the paths! export ANDROIDSDK="$HOME/Documents/android-sdk-27" export ANDROIDNDK="$HOME/Documents/android-ndk-r23b" export ANDROIDAPI="27" # Target API version of your application export NDKAPI="21" # Minimum supported API version of your application export ANDROIDNDKVER="r10e" # Version of the NDK you installed
You have the possibility to configure on any command the PATH to the SDK, NDK and Android API using:
--sdk-dir PATH
as an equivalent of $ANDROIDSDK--ndk-dir PATH
as an equivalent of $ANDROIDNDK--android-api VERSION
as an equivalent of $ANDROIDAPI--ndk-api VERSION
as an equivalent of $NDKAPI--ndk-version VERSION
as an equivalent of $ANDROIDNDKVER
Usage
Build a Kivy or SDL2 application
To build your application, you need to specify name, version, a package
identifier, the bootstrap you want to use (sdl2 for kivy or sdl2 apps)
and the requirements:
p4a apk --private $HOME/code/myapp --package=org.example.myapp --name "My application" --version 0.1 --bootstrap=sdl2 --requirements=python3,kivy
Note on --requirements
: you must add all
libraries/dependencies your app needs to run.
Example: --requirements=python3,kivy,vispy
. For an SDL2 app,
kivy is not needed, but you need to add any wrappers you might
use (e.g. pysdl2).
This p4a apk … command builds a distribution with python3,
kivy, and everything else you specified in the requirements.
It will be packaged using a SDL2 bootstrap, and produce
an .apk file.
Compatibility notes:
- Python 2 is no longer supported by python-for-android. The last release supporting Python 2 was v2019.10.06.
Build a WebView application
To build your application, you need to have a name, version, a package
identifier, and explicitly use the webview bootstrap, as
well as the requirements:
p4a apk --private $HOME/code/myapp --package=org.example.myapp --name "My WebView Application" --version 0.1 --bootstrap=webview --requirements=flask --port=5000
Please note as with kivy/SDL2, you need to specify all your
additional requirements/dependencies.
You can also replace flask with another web framework.
Replace --port=5000
with the port on which your app will serve a
website. The default for Flask is 5000.
Build a Service library archive
To build an android archive (.aar), containing an android service , you need a name, version, package identifier, explicitly use the
service_library bootstrap, and declare service entry point (See :ref:`services <arbitrary_scripts_services>` for more options), as well as the requirements and arch(s):
p4a aar --private $HOME/code/myapp --package=org.example.myapp --name "My library" --version 0.1 --bootstrap=service_library --requirements=python3 --release --service=myservice:service.py --arch=arm64-v8a --arch=armeabi-v7a
You can then call the generated Java entrypoint(s) for your Python service(s) in other apk build frameworks.
Exporting the Android App Bundle (aab) for distributing it on Google Play
Starting from August 2021 for new apps and from November 2021 for updates to existings apps,
Google Play Console will require the Android App Bundle instead of the long lived apk.
python-for-android handles by itself the needed work to accomplish the new requirements:
p4a aab —private $HOME/code/myapp —package=org.example.myapp —name=»My App» —version 0.1 —bootstrap=sdl2 —requirements=python3,kivy —arch=arm64-v8a —arch=armeabi-v7a —release
This p4a aab … command builds a distribution with python3,
kivy, and everything else you specified in the requirements.
It will be packaged using a SDL2 bootstrap, and produce
an .aab file that contains binaries for both armeabi-v7a and arm64-v8a ABIs.
The Android App Bundle, is supposed to be used for distributing your app.
If you need to test it locally, on your device, you can use bundletool <https://developer.android.com/studio/command-line/bundletool>
Other options
You can pass other command line arguments to control app behaviours
such as orientation, wakelock and app permissions. See
:ref:`bootstrap_build_options`.
Rebuild everything
If anything goes wrong and you want to clean the downloads and builds to retry everything, run:
p4a clean_all
If you just want to clean the builds to avoid redownloading dependencies, run:
p4a clean_builds && p4a clean_dists
Getting help
If something goes wrong and you don’t know how to fix it, add the
--debug
option and post the output log to the kivy-users Google
group or the
kivy #support Discord channel.
See :doc:`troubleshooting` for more information.
Advanced usage
Recipe management
You can see the list of the available recipes with:
p4a recipes
If you are contributing to p4a and want to test a recipes again,
you need to clean the build and rebuild your distribution:
p4a clean_recipe_build RECIPENAME p4a clean_dists # then rebuild your distribution
You can write «private» recipes for your application, just create a
p4a-recipes
folder in your build directory, and place a recipe in
it (edit the __init__.py
):
mkdir -p p4a-recipes/myrecipe touch p4a-recipes/myrecipe/__init__.py
Distribution management
Every time you start a new project, python-for-android will internally
create a new distribution (an Android build project including Python
and your other dependencies compiled for Android), according to the
requirements you added on the command line. You can force the reuse of
an existing distribution by adding:
p4a apk --dist_name=myproject ...
This will ensure your distribution will always be built in the same
directory, and avoids using more disk space every time you adjust a
requirement.
You can list the available distributions:
p4a distributions
And clean all of them:
p4a clean_dists
Configuration file
python-for-android checks in the current directory for a configuration
file named .p4a
. If found, it adds all the lines as options to the
command line. For example, you can add the options you would always
include such as:
--dist_name my_example --android_api 27 --requirements kivy,openssl
Overriding recipes sources
You can override the source of any recipe using the
$P4A_recipename_DIR
environment variable. For instance, to test
your own Kivy branch you might set:
export P4A_kivy_DIR=/home/username/kivy
The specified directory will be copied into python-for-android instead
of downloading from the normal url specified in the recipe.
setup.py file (experimental)
If your application is also packaged for desktop using setup.py,
you may want to use your setup.py instead of the
--requirements
option to avoid specifying things twice.
For that purpose, check out :doc:`distutils`
Going further
See the other pages of this doc for more information on specific topics:
- :doc:`buildoptions`
- :doc:`commands`
- :doc:`recipes`
- :doc:`bootstraps`
- :doc:`apis`
- :doc:`troubleshooting`
- :doc:`launcher`
- :doc:`contribute`
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Build Cross-Platform GUI Apps With Kivy
These days, developers are highly likely to be working on a mobile or web application. Python doesn’t have built-in mobile development capabilities, but there are packages you can use to create mobile applications, like Kivy, PyQt, or even Beeware’s Toga library.
These libraries are all major players in the Python mobile space. However, there are some benefits you’ll see if you choose to create mobile applications with Kivy. Not only will your application look the same on all platforms, but you also won’t need to compile your code after every change. What’s more, you’ll be able to use Python’s clear syntax to build your applications.
In this tutorial, you’ll learn how to:
- Work with Kivy widgets
- Lay out the UI
- Add events
- Use the KV language
- Create a calculator application
- Package your application for iOS, Android, Windows, and macOS
This tutorial assumes you’re familiar with object-oriented programming. If you’re not, then check out Object-Oriented Programming (OOP) in Python 3.
Let’s get started!
Understanding the Kivy Framework
Kivy was first released in early 2011. This cross-platform Python framework can be deployed to Windows, Mac, Linux, and Raspberry Pi. It supports multitouch events in addition to regular keyboard and mouse inputs. Kivy even supports GPU acceleration of its graphics, since they’re built using OpenGL ES2. The project uses the MIT license, so you can use this library for free and commercial software.
When you create an application with Kivy, you’re creating a Natural User Interface or NUI. The idea behind a Natural User Interface is that the user can easily learn how to use your software with little to no instruction.
Kivy does not attempt to use native controls or widgets. All of its widgets are custom-drawn. This means that Kivy applications will look the same across all platforms. However, it also means that your app’s look and feel will differ from your user’s native applications. This could be a benefit or a drawback, depending on your audience.
Installing Kivy
Kivy has many dependencies, so it’s recommended that you install it into a Python virtual environment. You can use either Python’s built-in venv
library or the virtualenv
package. If you’ve never used a Python virtual environment before, then check out Python Virtual Environments: A Primer.
Here’s how you can create a Python virtual environment:
$ python3 -m venv my_kivy_project
This will copy your Python 3 executable into a folder called my_kivy_project
and add a few other subfolders to that directory.
To use your virtual environment, you need to activate it. On Mac and Linux, you can do that by executing the following while inside the my_kivy_project
folder:
The command for Windows is similar, but the location of the activate script is inside of the Scripts
folder instead of bin
.
Now that you have an activated Python virtual environment, you can run pip
to install Kivy. On Linux and Mac, you’ll run the following command:
$ python -m pip install kivy
On Windows, installation is a bit more complex. Check out the official documentation for how to install Kivy on Windows. (Mac users can also download a dmg
file and install Kivy that way.)
If you run into any issues installing Kivy on your platform, then see the Kivy download page for additional instructions.
Working With Kivy Widgets
A widget is an onscreen control that the user will interact with. All graphical user interface toolkits come with a set of widgets. Some common widgets that you may have used include buttons, combo boxes, and tabs. Kivy has many widgets built into its framework.
Running a “Hello, Kivy!” Program
To see how Kivy works, take a look at the following “Hello, World!” application:
from kivy.app import App
from kivy.uix.label import Label
class MainApp(App):
def build(self):
label = Label(text='Hello from Kivy',
size_hint=(.5, .5),
pos_hint={'center_x': .5, 'center_y': .5})
return label
if __name__ == '__main__':
app = MainApp()
app.run()
Every Kivy application needs to subclass App
and override build()
. This is where you’ll put your UI code or make calls to other functions that define your UI code. In this case, you create a Label
widget and pass in its text
, size_hint
, and pos_hint
. These last two arguments are not required.
size_hint
tells Kivy the proportions to use when creating the widget. It takes two numbers:
- The first number is the
x
size hint and refers to the width of the control. - The second number is the
y
size hint and refers to the height of the control.
Both of these numbers can be anywhere between 0 and 1. The default value for both hints is 1. You can also use pos_hint
to position the widget. In the code block above, you tell Kivy to center the widget on the x and y axes.
To make the application run, you instantiate your MainApp
class and then call run()
. When you do so, you should see the following on your screen:
Kivy also outputs a lot of text to stdout
:
[INFO ] [Logger ] Record log in /home/mdriscoll/.kivy/logs/kivy_19-06-07_2.txt
[INFO ] [Kivy ] v1.11.0
[INFO ] [Kivy ] Installed at "/home/mdriscoll/code/test/lib/python3.6/site-packages/kivy/__init__.py"
[INFO ] [Python ] v3.6.7 (default, Oct 22 2018, 11:32:17)
[GCC 8.2.0]
[INFO ] [Python ] Interpreter at "/home/mdriscoll/code/test/bin/python"
[INFO ] [Factory ] 184 symbols loaded
[INFO ] [Image ] Providers: img_tex, img_dds, img_sdl2, img_gif (img_pil, img_ffpyplayer ignored)
[INFO ] [Text ] Provider: sdl2(['text_pango'] ignored)
[INFO ] [Window ] Provider: sdl2(['window_egl_rpi'] ignored)
[INFO ] [GL ] Using the "OpenGL" graphics system
[INFO ] [GL ] Backend used <sdl2>
[INFO ] [GL ] OpenGL version <b'4.6.0 NVIDIA 390.116'>
[INFO ] [GL ] OpenGL vendor <b'NVIDIA Corporation'>
[INFO ] [GL ] OpenGL renderer <b'NVS 310/PCIe/SSE2'>
[INFO ] [GL ] OpenGL parsed version: 4, 6
[INFO ] [GL ] Shading version <b'4.60 NVIDIA'>
[INFO ] [GL ] Texture max size <16384>
[INFO ] [GL ] Texture max units <32>
[INFO ] [Window ] auto add sdl2 input provider
[INFO ] [Window ] virtual keyboard not allowed, single mode, not docked
[INFO ] [Base ] Start application main loop
[INFO ] [GL ] NPOT texture support is available
This is useful for debugging your application.
Next, you’ll try adding an Image
widget and see how that differs from a Label
.
Displaying an Image
Kivy has a couple of different image-related widgets to choose from. You can use Image
to load local images from your hard drive or AsyncImage
to load an image from a URL. For this example, you’ll stick with the standard Image
class:
from kivy.app import App
from kivy.uix.image import Image
class MainApp(App):
def build(self):
img = Image(source='/path/to/real_python.png',
size_hint=(1, .5),
pos_hint={'center_x':.5, 'center_y':.5})
return img
if __name__ == '__main__':
app = MainApp()
app.run()
In this code, you import Image
from the kivy.uix.image
sub-package. The Image
class takes a lot of different parameters, but the one that you want to use is source
. This tells Kivy which image to load. Here, you pass a fully-qualified path to the image. The rest of the code is the same as what you saw in the previous example.
When you run this code, you’ll see something like the following:
The text from the previous example has been replaced with an image.
Now you’ll learn how to add and arrange multiple widgets in your application.
Laying Out the UI
Each GUI framework that you use has its own method of arranging widgets. For example, in wxPython you’ll use sizers, while in Tkinter you use a layout or geometry manager. With Kivy, you’ll use Layouts. There are several different types of Layouts that you can use. Here are some of the most common ones:
BoxLayout
FloatLayout
GridLayout
You can search Kivy’s documentation for a full list of available Layouts. You can also look in kivy.uix
for the actual source code.
Try out the BoxLayout
with this code:
import kivy
import random
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
red = [1,0,0,1]
green = [0,1,0,1]
blue = [0,0,1,1]
purple = [1,0,1,1]
class HBoxLayoutExample(App):
def build(self):
layout = BoxLayout(padding=10)
colors = [red, green, blue, purple]
for i in range(5):
btn = Button(text="Button #%s" % (i+1),
background_color=random.choice(colors)
)
layout.add_widget(btn)
return layout
if __name__ == "__main__":
app = HBoxLayoutExample()
app.run()
Here, you import BoxLayout
from kivy.uix.boxlayout
and instantiate it. Then you create a list of colors, which are themselves lists of Red-Blue-Green (RGB) colors. Finally, you loop over a range
of 5, creating a button btn
for each iteration. To make things a bit more fun, you set the background_color
of the button to a random color. You then add the button to your layout with layout.add_widget(btn)
.
When you run this code, you’ll see something like this:
There are 5 randomly-colored buttons, one for each iteration of your for
loop.
When you create a layout, there are a few arguments you should know:
padding
: You can specify thepadding
in pixels between the layout and its children in one of three ways:- A four-argument list: [
padding_left
,padding_top
,padding_right
,padding_bottom
] - A two-argument list: [
padding_horizontal
,padding_vertical
] - A singular argument:
padding=10
- A four-argument list: [
spacing
: You can add space between the children widgets with this argument.orientation
: You can change the defaultorientation
of theBoxLayout
from horizontal to vertical.
Adding Events
Like most GUI toolkits, Kivy is mostly event-based. The framework responds to user keypresses, mouse events, and touch events. Kivy has the concept of a Clock that you can use to schedule function calls for some time in the future.
Kivy also has the concept of Properties
, which works with the EventDispatcher
. Properties help you do validation checking. They also let you fire events whenever a widget changes its size or position.
Let’s add a button event to your button code from earlier:
from kivy.app import App
from kivy.uix.button import Button
class MainApp(App):
def build(self):
button = Button(text='Hello from Kivy',
size_hint=(.5, .5),
pos_hint={'center_x': .5, 'center_y': .5})
button.bind(on_press=self.on_press_button)
return button
def on_press_button(self, instance):
print('You pressed the button!')
if __name__ == '__main__':
app = MainApp()
app.run()
In this code, you call button.bind()
and link the on_press
event to MainApp.on_press_button()
. This method implicitly takes in the widget instance
, which is the button
object itself. Finally, a message will print to stdout
whenever the user presses your button.
Using the KV Language
Kivy also provides a design language called KV that you can use with your Kivy applications. The KV language lets you separate your interface design from the application’s logic. This follows the separation of concerns principle and is part of the Model-View-Controller architectural pattern. You can update the previous example to use the KV language:
from kivy.app import App
from kivy.uix.button import Button
class ButtonApp(App):
def build(self):
return Button()
def on_press_button(self):
print('You pressed the button!')
if __name__ == '__main__':
app = ButtonApp()
app.run()
This code might look a bit odd at first glance, as it creates a Button
without setting any of its attributes or binding it to any events. What’s happening here is that Kivy will automatically look for a file that has the same name as the class in lowercase, without the App
part of the class name.
In this case, the class name is ButtonApp
, so Kivy will look for a file named button.kv
. If that file exists and is properly formatted, then Kivy will use it to load up the UI. Go ahead and create this file and add the following code:
1<Button>:
2 text: 'Press me'
3 size_hint: (.5, .5)
4 pos_hint: {'center_x': .5, 'center_y': .5}
5 on_press: app.on_press_button()
Here’s what each line does:
- Line 1 matches the
Button
call in your Python code. It tells Kivy to look into the instantiated object for a button definition. - Line 2 sets the button’s
text
. - Line 3 sets the width and height with
size_hint
. - Line 4 sets the button’s position with
pos_hint
. - Line 5 sets the
on_press
event handler. To tell Kivy where the event handler is, you useapp.on_press_button()
. Here, Kivy knows will look in theApplication
class for a method called.on_press_button()
.
You can set up all of your widgets and layouts inside one or more KV language files. The KV language also supports importing Python modules in KV, creating dynamic classes, and much more. For full details, check out Kivy’s guide to the KV Language.
Now you’re ready to create a real application!
Creating a Kivy Application
One of the best ways to learn a new skill is by creating something useful. With that in mind, you’ll use Kivy to build a calculator that supports the following operations:
- Addition
- Subtraction
- Multiplication
- Division
For this application, you’ll need a series of buttons in some kind of layout. You’ll also need a box along the top of your app to display the equations and their results. Here’s a sketch of your calculator:
Now that you have a goal for the UI, you can go ahead and write the code:
1from kivy.app import App
2from kivy.uix.boxlayout import BoxLayout
3from kivy.uix.button import Button
4from kivy.uix.textinput import TextInput
5
6class MainApp(App):
7 def build(self):
8 self.operators = ["/", "*", "+", "-"]
9 self.last_was_operator = None
10 self.last_button = None
11 main_layout = BoxLayout(orientation="vertical")
12 self.solution = TextInput(
13 multiline=False, readonly=True, halign="right", font_size=55
14 )
15 main_layout.add_widget(self.solution)
16 buttons = [
17 ["7", "8", "9", "/"],
18 ["4", "5", "6", "*"],
19 ["1", "2", "3", "-"],
20 [".", "0", "C", "+"],
21 ]
22 for row in buttons:
23 h_layout = BoxLayout()
24 for label in row:
25 button = Button(
26 text=label,
27 pos_hint={"center_x": 0.5, "center_y": 0.5},
28 )
29 button.bind(on_press=self.on_button_press)
30 h_layout.add_widget(button)
31 main_layout.add_widget(h_layout)
32
33 equals_button = Button(
34 text="=", pos_hint={"center_x": 0.5, "center_y": 0.5}
35 )
36 equals_button.bind(on_press=self.on_solution)
37 main_layout.add_widget(equals_button)
38
39 return main_layout
Here’s how your calculator code works:
- In lines 8 to 10, you create a list of
operators
and a couple of handy values,last_was_operator
andlast_button
, that you’ll use later on. - In lines 11 to 15, you create a top-level layout
main_layout
and add a read-onlyTextInput
widget to it. - In lines 16 to 21, you create a nested list of lists containing most of your
buttons
for the calculator. - In line 22, you start a
for
loop over thosebuttons
. For each nested list you’ll do the following:- In line 23, you create a
BoxLayout
with a horizontal orientation. - In line 24, you start another
for
loop over the items in the nested list. - In lines 25 to 39, you create the buttons for the row, bind them to an event handler, and add the buttons to the horizontal
BoxLayout
from line 23. - In line 31, you add this layout to
main_layout
.
- In line 23, you create a
- In lines 33 to 37, you create the equals button (
=
), bind it to an event handler, and add it tomain_layout
.
The next step is to create the .on_button_press()
event handler. Here’s what that code looks like:
41def on_button_press(self, instance):
42 current = self.solution.text
43 button_text = instance.text
44
45 if button_text == "C":
46 # Clear the solution widget
47 self.solution.text = ""
48 else:
49 if current and (
50 self.last_was_operator and button_text in self.operators):
51 # Don't add two operators right after each other
52 return
53 elif current == "" and button_text in self.operators:
54 # First character cannot be an operator
55 return
56 else:
57 new_text = current + button_text
58 self.solution.text = new_text
59 self.last_button = button_text
60 self.last_was_operator = self.last_button in self.operators
Most of the widgets in your application will call .on_button_press()
. Here’s how it works:
-
Line 41 takes the
instance
argument so you can access which widget called the function. -
Lines 42 and 43 extract and store the value of the
solution
and the buttontext
. -
Lines 45 to 47 check to see which button was pressed. If the user pressed
C
, then you’ll clear thesolution
. Otherwise, move on to theelse
statement. -
Line 49 checks if the solution has any pre-existing value.
-
Line 50 to 52 check if the last button pressed was an operator button. If it was, then
solution
won’t be updated. This is to prevent the user from having two operators in a row. For example,1 */
is not a valid statement. -
Lines 53 to 55 check to see if the first character is an operator. If it is, then
solution
won’t be updated, since the first value can’t be an operator value. -
Lines 56 to 58 drop to the
else
clause. If none of the previous conditions are met, then updatesolution
. -
Line 59 sets
last_button
to the label of the last button pressed. -
Line 60 sets
last_was_operator
toTrue
orFalse
depending on whether or not it was an operator character.
The last bit of code to write is .on_solution()
:
62def on_solution(self, instance):
63 text = self.solution.text
64 if text:
65 solution = str(eval(self.solution.text))
66 self.solution.text = solution
Once again, you grab the current text from solution
and use Python’s built-in eval()
to execute it. If the user created a formula like 1+2
, then eval()
will run your code and return the result. Finally, you set the result as the new value for the solution
widget.
When you run this code, your application will look like this on a desktop computer:
To see the full code for this example, expand the code block below.
Here’s the full code for the calculator:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
class MainApp(App):
def build(self):
self.operators = ["/", "*", "+", "-"]
self.last_was_operator = None
self.last_button = None
main_layout = BoxLayout(orientation="vertical")
self.solution = TextInput(
multiline=False, readonly=True, halign="right", font_size=55
)
main_layout.add_widget(self.solution)
buttons = [
["7", "8", "9", "/"],
["4", "5", "6", "*"],
["1", "2", "3", "-"],
[".", "0", "C", "+"],
]
for row in buttons:
h_layout = BoxLayout()
for label in row:
button = Button(
text=label,
pos_hint={"center_x": 0.5, "center_y": 0.5},
)
button.bind(on_press=self.on_button_press)
h_layout.add_widget(button)
main_layout.add_widget(h_layout)
equals_button = Button(
text="=", pos_hint={"center_x": 0.5, "center_y": 0.5}
)
equals_button.bind(on_press=self.on_solution)
main_layout.add_widget(equals_button)
return main_layout
def on_button_press(self, instance):
current = self.solution.text
button_text = instance.text
if button_text == "C":
# Clear the solution widget
self.solution.text = ""
else:
if current and (
self.last_was_operator and button_text in self.operators):
# Don't add two operators right after each other
return
elif current == "" and button_text in self.operators:
# First character cannot be an operator
return
else:
new_text = current + button_text
self.solution.text = new_text
self.last_button = button_text
self.last_was_operator = self.last_button in self.operators
def on_solution(self, instance):
text = self.solution.text
if text:
solution = str(eval(self.solution.text))
self.solution.text = solution
if __name__ == "__main__":
app = MainApp()
app.run()
It’s time to deploy your application!
Packaging Your App for Android
Now that you’ve finished the code for your application, you can share it with others. One great way to do that is to turn your code into an application that can run on your Android phone. To accomplish this, first you’ll need to install a package called buildozer
with pip
:
Then, create a new folder and navigate to it in your terminal. Once you’re there, you’ll need to run the following command:
This will create a buildozer.spec
file that you’ll use to configure your build. For this example, you can edit the first few lines of the spec file as follows:
[app]
# (str) Title of your application
title = KvCalc
# (str) Package name
package.name = kvcalc
# (str) Package domain (needed for android/ios packaging)
package.domain = org.kvcalc
Feel free to browse the rest of the file to see what else you can change.
At this point, you’re almost ready to build your application, but first, you’ll want to install the dependencies for buildozer
. Once those are installed, copy your calculator application into your new folder and rename it to main.py
. This is required by buildozer
. If you don’t have the file named correctly, then the build will fail.
Now you can run the following command:
$ buildozer -v android debug
The build step takes a long time! On my machine, it took 15 to 20 minutes. Depending on your hardware, it may take even longer, so feel free to grab a cup of coffee or go for a run while you wait. Buildozer
will download whatever Android SDK pieces it needs during the build process. If everything goes according to plan, then you’ll have a file named something like kvcalc-0.1-debug.apk
in your bin
folder.
The next step is to connect your Android phone to your computer and copy the apk
file to it. Then you can open the file browser on your phone and click on the apk
file. Android should ask you if you’d like to install the application. You may see a warning since the app was downloaded from outside Google Play, but you should still be able to install it.
Here’s the calculator running on my Samsung S9:
The buildozer
tool has several other commands you can use. Check out the documentation to see what else you can do.
You can also package the app using python-for-android
if you need more fine-grained control. You won’t cover this here, but if you’re interested, check out the project’s quickstart.
Packaging Your App for iOS
The instructions for building an application for iOS are a bit more complex than Android. For the most up-to-date information, you should always use Kivy’s official packaging documentation. You’ll need to run the following commands before you can package your application for iOS on your Mac:
$ brew install autoconf automake libtool pkg-config
$ brew link libtool
$ sudo easy_install pip
$ sudo pip install Cython==0.29.10
Once those are all installed successfully, you’ll need to compile the distribution using the following commands:
$ git clone git://github.com/kivy/kivy-ios
$ cd kivy-ios
$ ./toolchain.py build python3 kivy
If you get an error that says iphonesimulator
can’t be found, then see this StackOverflow answer for ways to solve that issue. Then try running the above commands again.
If you run into SSL errors, then you probably don’t have Python’s OpenSSL setup. This command should fix that:
$ cd /Applications/Python 3.7/
$ ./Install Certificates.command
Now go back and try running the toolchain
command again.
Once you’ve run all the previous commands successfully, you can create your Xcode project using the toolchain
script. Your main application’s entry point must be named main.py
before you create the Xcode project. Here is the command you’ll run:
./toolchain.py create <title> <app_directory>
There should be a directory named title
with your Xcode project in it. Now you can open that project in Xcode and work on it from there. Note that if you want to submit your application to the App Store, then you’ll have to create a developer account at developer.apple.com and pay their yearly fee.
Packaging Your App for Windows
You can package your Kivy application for Windows using PyInstaller. If you’ve never used it before, then check out Using PyInstaller to Easily Distribute Python Applications.
You can install PyInstaller using pip
:
$ pip install pyinstaller
The following command will package your application:
This command will create a Windows executable and several other files. The -w
argument tells PyInstaller that this is a windowed application, rather than a command-line application. If you’d rather have PyInstaller create a single executable file, then you can pass in the --onefile
argument in addition to -w
.
Packaging Your App for macOS
You can use PyInstaller to create a Mac executable just like you did for Windows. The only requirement is that you run this command on a Mac:
$ pyinstaller main.py -w --onefile
This will create a single file executable in the dist
folder. The executable will be the same name as the Python file that you passed to PyInstaller. If you’d like to reduce the file size of the executable, or you’re using GStreamer in your application, then check out Kivy’s packaging page for macOS for more information.
Conclusion
Kivy is a really interesting GUI framework that you can use to create desktop user interfaces and mobile applications on both iOS and Android. Kivy applications will not look like the native apps on any platform. This can be an advantage if you want your application to look and feel different from the competition!
In this tutorial, you learned the basics of Kivy including how to add widgets, hook up events, lay out multiple widgets, and use the KV language. Then you created your first Kivy application and learned how to distribute it on other platforms, including mobile!
There are many widgets and concepts about Kivy that you didn’t cover here, so be sure to check out Kivy’s website for tutorials, sample applications, and much more.
Further Reading
To learn more about Kivy, check out these resources:
- Kivy Programming Guide
- Kivy Packaging Documentation
- Build Desktop GUI Apps Using Python
To see how you might create a desktop application with another Python GUI framework, check out How to Build a Python GUI Application With wxPython.
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Build Cross-Platform GUI Apps With Kivy