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

0
1092

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

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

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

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

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

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

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

 

 

 

 

 

 

 

 

 

 

 

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

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

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

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

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

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

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

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

<ProductScreen@Screen>:

    ...

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

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

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

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

MDToolbar:
    ...
    pos_hint: {"top": 1}

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

pos_hint: {"top": 1}  # верхняя граница экрана
pos_hint: {"bottom": 1}  # нижняя граница экрана
pos_hint: {"right": 1}  # правая граница экрана
pos_hint: {"center_y": .5}  # центр экрана по вертикали
pos_hint: {"center_x": .2}  # отступ в 20 % по горизонтали от левой границы экрана
...
...

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

BoxLayout:
        size_hint_y: None
        height: root.height - toolbar.height

        FitImage:
            source: "smokestackheather.jpeg"

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

Разработка мобильных приложений на Python. Библиотека 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()

И получите результат:

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

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

from kivy.app import App
from kivy.lang import Builder

KV = """
Button:
    text: "Button"
    size_hint: None, None
    size: 100, 50
    pos_hint: {"center_y": .5, "center_x": .5}
"""


class MyApp(App):
    def build(self):
        return Builder.load_string(KV)


MyApp().run()

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

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

Также можно указать свойство 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()

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

Или тоже самое, но подсказка ширины (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()

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

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

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

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

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

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

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

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

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

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

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

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

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

Список изображений:

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

Если бы мы захотели использовать вертикальную прокрутку списка, нам нужно было бы изменить 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 {i}",
                    size_hint_y=None,
                    height=dp(40),
                )
            )


MyApp().run()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 

 

 

 

 

 

 

 

 

 

Полный список параметров модуля screen:

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

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

}

Ну, и, наконец, финальный результат — запуск на мобильном устройстве…

Единственное, что огорчает, это скорость запуска. У того же Flutter она просто феноменальная!

Надеюсь, был кому-то полезен, до новых встреч!

источник