Кроссплатформенная разработка мобильных приложений python

Кроссплатформенные OpenGL + Python при помощи Kivy

Предыстория: будучи наивным чукотским программистом, я думал: «питон такой кроссплатформенный, напишу игрушку для сына, запущу на планшетике, пусть играется». В результате две недели ушло на попытки натыкать решение по переезду с PyOpenGL+pygame на kivy, так как внятного примера использования OpenGL с kivy не нашел. Возможно, кому-то мой опыт поможет сэкономить время.

Примерная схема процесса

  1. Никого не призываю разрабатывать на python приложения с 3д-графикой под андроид. Это не самый шустрый вариант. Но если очень хочется, то можно читать дальше.
  2. В kivy есть встроенное-своё-родное решение Mesh, которое умеет в 3д-графику. С примерами тоже не всё так просто. Я предпочел голый OpenGL.
  3. Возможно, я открыл Америку в попытках найти Индию. Надеюсь, более опытные товарищи поправят и дополнят.

Перед тем, как начать работать с 3D графикой в kivy, стоит почитать про библиотеку вообще и про её установку (например, мануалы рекомендуют использовать виртуальную среду).

В чем проблема? Описание функций OpenGL разработчики kivy коварно прячут на своём официальном сайте. Вопрос только в том, как заставить это работать.

За исключением нескольких нюансов, переезд с PyOpenGL+pygame на kivy касательно использования функций собственно OpenGL заключается в замене строчек:

from OpenGL.GL import * from OpenGL.GL.shaders import *
from kivy.graphics.opengl import *

Нюансы: PyOpenGL работает с массивами numpy напрямую, в kivy приходится преобразовывать их функцией tobytes(). Для загрузки текстур в kivy используется glPixelStorei и glTexImage2D вместо glTexStorage2D и glTexSubImage2D. Шейдеры под андроид должны быть под версию 2 (без in, out и прочих излишеств) и иметь в начале код:

#ifdef GL_ES precision highp float; #endif

Далее самое интересное — это всё надо как-то подключить к выводу на экран. В связке PyOpenGL+pygame код имеет такую структуру:

#Было: import pygame from pygame.locals import * def init(): pygame.init() pygame.display.set_mode((Width, Height), HWSURFACE | OPENGL | DOUBLEBUF) ''' дальше идут функции, инициализирующие работу с PyOpenGL ''' def main(): init() while True: ''' меняем вершины и запускаем отрисовку ''' pygame.display.flip()

В kivy надо создать класс приложения на основе класса App, в котором необходимо сослаться на виджет, унаследованный от класса Widget. Кроме того, нам понадобится объект Window, который автоматически создается при работе с kivy с учетом используемой операционной системы. Базовый класс у Window — WindowBase.

Читайте также:  Графические средства разработки приложений

Таким образом структура приложения будет примерно такой:

#Стало: from kivy.app import App from kivy.uix.widget import Widget from kivy.core.window import Window from kivy.base import EventLoop from kivy.clock import Clock def init(): ''' почти те же функции, инициализирующие работу с OpenGL ''' init() class CustomWidget(Widget): def __init__(self, **kwargs): super(CustomWidget, self).__init__(**kwargs) def update_glsl(self, nap): ''' здесь меняем вершины и запускаем отрисовку ''' Window.flip() class MainApp(App): def build(self): root = CustomWidget() EventLoop.ensure_window() return root def on_start(self): Clock.schedule_interval(self.root.update_glsl, 40 ** -1) #это наш желательный FPS if __name__ == '__main__': MainApp().run()

Этот код уже покажет картинку (если добавить соответствующий функционал OpenGL), но картинка будет мерзко мигать. Это происходит потому, что у объекта Window есть свой вызов функции flip(), который вставляет чёрный экран в наш вывод. Этот вызов описан в упомянутом классе WindowBase. Добавим следующий код, отключающий лишний флип, в описание класса MainApp:

def passFunc(W): pass class MainApp(App): def build(self): root = CustomWidget() EventLoop.ensure_window() #вот тут отключаем лишний флип: Window.on_flip = lambda W = Window: passFunc(W) return root

Что дальше? После заполнения пробелов в этом коде, между программистом и apk-файлом остаётся только билдозер. Вот мануал, которого достаточно даже для непродвинутого юзера вроде меня (см. также подпись к видео, там есть скрипт, сильно упрощающий работу):

Кроме того в spec-файле рекомендую включить полноэкранный режим для пущего фпс.

Example вот. Код рисует инопланетянина с логотипа андроида (как на КДПВ), позволяет его вращать с помощью мышки/сенсора.

А что там всё-таки с производительностью? Ну такое. Написал небольшую игрушку — змейку в трехмерном пространстве. FPS на самсунг А51 болтается между 15 и 25. Видео записано с телефона:

Сын, между тем, отказался играться в моё поделие, ну хотя бы смотрит с интересом. Плоские рисованные игры двухлетке, оказывается, лучше заходят. Вот ради чего всё начиналось:

Источник

Разработка мобильных приложений на Python. Библиотека KivyMD

Приветствую! Сегодня речь снова пойдет о библиотеке KivyMD — наборе виджетов для кроссплатформенной разработки на Python в стиле Material Design. В этой статье я сделаю не обзор виджетов KivyMD, как в недавней статье, а, скорее, это будет материал больше о позиционировании виджетов. Что-то похожего на туториал по разработке мобильных приложений на Python для новичков здесь не будет, так что если впервые слышите о фреймворке Kivy, вряд ли вам будет все это интересно. Ну, а мы погнали под кат!

На днях скачал из Google Play демонстрационное приложение Flutter UIKit:

И сейчас мы с вами попробуем повторить один экран из этого приложения. Давайте сразу посмотрим на результаты: слева — Flutter, справа — Kivy & KivyMD.

Некоторые элементы UI отличаются, не в силу каких-то технических особенностей, из-за которых нельзя было получить идентичный результат, а просто я посчитал, что так будет более органичней (например, черный Toolbar, по моему мнению, совсем не смотрится).

Итак! Что бросается в глаза, глядя на экран, который мы будем воспроизводить? Прозрачный фон переднего layout. В Kivy такую возможность предоставляет FloatLayout, который позволяет размещать в себе виджеты и контроллы один над другим следующим образом:

Схематично наш экран будет выглядеть так:

Разметка этого экрана довольно простая:

Почему я говорю о FloatLayout, если наш экран унаследован от Screen?

Просто потому, что Screen —> RelativeLayout —> FloatLayout.

Все виджеты во FloatLayout позиционируются от нижнего левого угла, то есть, на экране им автоматически присваивается позиция (0, 0). В разметке не сложно проследить порядок добавления элементов на экран сверху вниз:

Если кто-то обратил внимание, то позицию мы указали только одному виджету:

Каждому виджету в Kivy помимо конкретных координат (x, y) можно указать подсказку позиции:

pos_hint: # верхняя граница экрана pos_hint: # нижняя граница экрана pos_hint: # правая граница экрана pos_hint: # центр экрана по вертикали pos_hint: # отступ в 20 % по горизонтали от левой границы экрана . . 

Так вот, нижнее фоновое изображение…

 BoxLayout: size_hint_y: None height: root.height - toolbar.height FitImage: source: "smokestackheather.jpeg" 

… благодаря виджету FitImage (библиотека KivyMD), автоматически растягивается на все выделенное ему пространство с сохранением пропорций изображения:

По умолчанию каждому виджету и лайоуту в Kivy предоставляется 100 % пространства, если не указанно иное. Например, если вы захотите добавить на экран одну кнопку, вы, очевидно сделаете следующее:

from kivy.app import App from kivy.lang import Builder KV = """ Button: text: "Button" """ class MyApp(App): def build(self): return Builder.load_string(KV) MyApp().run() 

Кнопка заняла 100 % пространства. Чтобы разместить кнопку по центру экрана, нужно, во-первых, задать ей необходимый размер и, во-вторых, указать, где она будет находится:

from kivy.app import App from kivy.lang import Builder KV = """ Button: text: "Button" size_hint: None, None size: 100, 50 pos_hint: """ class MyApp(App): def build(self): return Builder.load_string(KV) MyApp().run() 

Теперь картина изменилась:

Также можно указать свойство size_hint, от 0 до 1, (эквивалент 0-100%), то есть, подсказка размера:

from kivy.app import App from kivy.lang import Builder KV = """ BoxLayout: Button: text: "Button" size_hint_y: .2 Button: text: "Button" size_hint_y: .1 Button: text: "Button" """ class MyApp(App): def build(self): return Builder.load_string(KV) MyApp().run() 

Или тоже самое, но подсказка ширины (size_hint_x):

from kivy.app import App from kivy.lang import Builder KV = """ BoxLayout: Button: text: "Button" size_hint_x: .2 Button: text: "Button" size_hint_x: .1 Button: text: "Button" """ class MyApp(App): def build(self): return Builder.load_string(KV) MyApp().run() 

MDToolbar имеет высоту в 56dp, не может занимать все пространство, и если ему не подсказать, что его место сверху, то он автоматически прилипнет к нижней части экрана:

Список карточек — OrderProductLayout (о нем мы поговорим ниже) — это ScrollView с элементами MDCard и он занимает всю высоту экрана, но благодаря padding (значения отступов в лайоутах) кажется, что он находится чуть выше центра экрана. Ну а MDBottomAppBar по умолчанию кидает якорь к нижней границе экрана. Поэтому только MDToolbar мы указали, где его место.

Теперь давайте посмотрим, что представляет из себя виджет OrderProductLayout:

Как видим, это четыре карточки, вложенные в ScrillView. В отличие от родительского экрана, который унаследован от FloatLayout, здесь все виджеты читаются сверху вниз.

Это очень удобно, поскольку прослеживается четкая иерархия виджетов, древовидная структура и с одного взгляда понятно, какой виджет/контролл какому лайоуту принадлежит. В Kivy наиболее частым используемым лайоутом является BoxLayout — коробка, которая позволяет размещать в себе виджеты по вертикали либо по горизонтали (по умолчанию — последнее):

Более наглядно это видно из следующей схемы, где используется BoxLayout горизонтальной ориентации:

Мы запретили BoxLayout использовать 100% пространства — size_hint_y: None и сказали — твоя высота будет ровно такой, какой будет высота самого высокого элемента, вложенного в тебя — height: self.minimum_height.

Если бы мы захотели использовать вертикальную прокрутку списка, нам нужно было бы изменить GridLayout следующим образом:

 ScrollView: GridLayout: size_hint_y: None height: self.minimum_height cols: 1 

Заменить строки (rows) на столбцы (cols) и указать в minimum не ширину, а высоту:

from kivy.app import App from kivy.lang import Builder from kivy.metrics import dp from kivy.uix.button import Button KV = """ ScrollView: GridLayout: id: box size_hint_y: None height: self.minimum_height spacing: "5dp" cols: 1 """ class MyApp(App): def build(self): return Builder.load_string(KV) def on_start(self): for i in range(20): self.root.ids.box.add_widget( Button( text=f"Label ", size_hint_y=None, height=dp(40), ) ) MyApp().run() 

Следующие карты — выбор цвета и размера (они практически идентичны):

Отличительной особенностью языка разметки Kv Language является не только четкая структура виджетов, но и то, что этот язык поддерживает некоторые возможности языка Python. А именно: вызов методов, создание/изменение переменных, логические, I/O и математические операции…

Вычисление значения value, объявленного в Label

 Label: value: 0 text: str(self.value) 

… происходит непосредственно в самой разметке:

 MDIconButton: on_release: label_value.value -= 1 if label_value.value > 0 else 0 

И я никогда не поверю, что вот это (код Flutter)…

… логичнее и читабельнее кода Kv Language:

Вчера меня спрашивали, как у Kivy обстоят дела со средой разработки, есть ли автокомплиты, хотрелоад и прочие прелести? С автокомплитами все отлично, если пользоваться PyCharm:

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

Слева — запущено на мобильном устройстве, справа — на компьютере:

Источник

Оцените статью