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

Для тех, кто незнаком ни с Kivy ни с библиотекой KivyMD, вкратце напомню:


Kivy — кроссплатформенный фреймворк с открытым исходным кодом, написанный с использованием Python/Cython. Поддерживает устройства ввода: WM_Touch, WM_Pen, Mac OS X Trackpad Magic Mouse, Mtdev, Linux Kernel HID, TUIO…


KivyMD — сторонняя open source библиотека, написанная на чистом Python, реализующая набор виджетов в стиле Google Material Design, для использования с фреймворком Kivy. Вне экосистемы Kivy библиотека не используется. Текущее состояние — бета (PIP версия 0.104.2). Мастер версия на GitHub — 1.0.0

Итак, что такое hero анимации?
Скорее всего, вы видели такую анимацию уже много раз. Например, на экране отображается список элементов, представляющих товары для продажи. Тап по элементу списка переносит этот элемент на новый экран, содержащий более подробную информацию и кнопку “Купить”. Перемещение элемента с одного экрана на другой называется hero анимацией (выдержка из документации Flutter).

Демонстрация hero анимации в KivyMD:



Использование hero анимации включает следующие шаги:

  • На экране A разместить контейнер MDHeroFrom;
  • Установить тег (строку) для контейнера MDHeroFrom;
  • Разместить героя (виджет, который будет перемещаться с одного экрана на другой) в MDHeroFrom контейнере;
  • На экране B разместить контейнер MDHeroTo — герой с экрана A переместится в этот контейнер.

Схематично это будет выглядеть следующим образом:


Код:

MDScreenManager:

    MDScreen:
        name: "screen A"

        MDHeroFrom:
            tag: "hero"

            FitImage:

    MDScreen:
        name: "screen B"

        MDHeroTo:

Мы будем рассматривать реализацию hero анимации на следующем примере (то, что в итоге должно получится):


Для работы нам нужно установить библиотеку KivyMD:

pip install https://github.com/kivymd/KivyMD/archive/master.zip

Это всё. Все необходимые зависимости устанавливаются автоматически.

Создание проекта


KivyMD предоставляет набор инструментов разработчика в виде консольной утилиты create_project для создания базового проекта с шаблоном MVC. О том как реализован этот шаблон в библиотеке вы можете прочитать в этой статье. Для создания проекта используется команда с рядом нижеследующих аргументов:

Шаблон команды:

python -m kivymd.tools.patterns.create_project \
    name_pattern \
    path_to_project \
    name_project \
    python_version \
    kivy_version

Пирмер команды:

python -m kivymd.tools.patterns.create_project \
    MVC \
    /Path/To/Project \
    HeroAnimation \
    python3.10 \
    2.1.0

Эта команда создаст проект с шаблоном MVC. Кроме того, в проекте будет создано виртуальное окружение с Python 3.10, с последней версией Kivy 2.1.0 и мастер версией (1.0.0-dev0) библиотеки KivyMD. Я не буду рассматривать все аргументы этой утилиты, если вам интересно, вы можете ознакомиться с ними в официальной документации. Нас интересует аргумент --name_screen — принимает имена Views (экраны приложения), которые будут включены при создании проекта. В нашем примере для создания простой hero анимации мы будем использовать два View. Назовем их FirstScreen и SecondScreen. Поэтому команда для создания проекта будет иметь вид:

python -m kivymd.tools.patterns.create_project \
    MVC \
    /Path/To/Project \
    HeroAnimation \
    python3.10 \
    2.1.0 \
    --name_screen FirstScreen SecondScreen

Эта команда создаст шаблон проекта с двумя View:


Итак, для начала мы должны создать виджет нашего героя. В пакете каждой вьюшки находится пакет components. Здесь вы можете размещать пакеты собственных или сторонних UI компонентов (виджетов) для конкретного View. Мы разместим там пакет, который реализует виджет героя:



В разметке героя мы помещаем в контейнер с вертикальной ориентацией контейнер MDHeroFrom и в нем размещаем изображение. Ниже располагаем метку с текстом:


При тапе по виджету героя будет вызван метод switch_screen, который реализован в базовом python классе героя. Метод установит имя текущего героя для экранного менеджера и имя View, который должен быть открыт:

from kivy.properties import ObjectProperty
from kivy.uix.behaviors import ButtonBehavior

from kivymd.uix.boxlayout import MDBoxLayout


class Hero(ButtonBehavior, MDBoxLayout):
    """Класс реализует виджет героя."""

    manager = ObjectProperty()  # объект менеджера экранов

    def switch_screen(self):
        """Метод, который вызывается при тапе по виджету героя."""

        # Устанавливает имя текущего героя для экранного менеджера.
        self.manager.current_hero = self.ids.hero.tag
        # Переключаем экран.
        self.manager.current = "second screen"

После того как виджет герой создан можем разместить его не первом View:

<FirstScreenView>
    md_bg_color: "white"

    Hero:
        pos_hint: {'center_x': .5, "center_y": .5}
        manager: root.manager_screens

На втором View нам нужно поместить виджет MDHeroTo — то место, куда будет прилетать герой:


Для View, который имеет виджет MDHeroTo, нужно передать ссылку на этот объект:

<SecondScreenView>
    hero_to: hero_to

Управление свойствами героя во время анимации


Анимация перелета героя осуществляется автоматически и анимируется только его позиция. Но если нам нужно дополнительно анимировать какие-либо свойства во время полета героя, кроме его позиции, мы можем использовать такие методы как on_transform_in — перелет с экрана А на экран Б и on_transform_out — возвращение с экрана Б на экран А. Давайте сделает так, чтобы герой вращался во время своего полета с одного экрана на другой. Для этого нужно использовать виджет RotateWiget. Изображение героя унаследуем от этого класса:

class HeroImage(RotateWidget, FitImage):
    pass

… и будем использовать его вместо обычного FitImage в разметке героя, привязав событие on_transform_in к вызову своего метода, в котором мы будем анимировать свойства вращения виджета изображения героя:

<Hero>
    [...]

    MDHeroFrom:
        [...]

        HeroImage:
            [...]
            on_transform_in: root.on_transform_in(*args)

    MDLabel:
        [...]

Теперь в класс Hero нам нужно добавить метод on_transform_in:

    def on_transform_in(self, instance_hero_widget, instance_image, duration):
        """Анимирует свойство вращения изображения героя."""

        Animation(rotate_value_angle=360, duration=duration).start(
            instance_image
        )

Класс героя полностью имеет следующую реализацию:

from kivy.animation import Animation
from kivy.properties import ObjectProperty
from kivy.uix.behaviors import ButtonBehavior

from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.fitimage import FitImage
from kivymd.uix.templates import RotateWidget


class HeroImage(RotateWidget, FitImage):
    """
    Реализует изображение героя со свойствами, позволяющими вращать виджет.
    """


class Hero(ButtonBehavior, RotateWidget, MDBoxLayout):
    """Класс реализует виджет героя."""

    manager = ObjectProperty()  # объект менеджера экранов

    def on_transform_in(self, instance_hero_widget, instance_image, duration):
        """Анимирует свойство вращения изображения героя на 360 градусов."""

        Animation(rotate_value_angle=360, duration=duration).start(
            instance_image
        )

    def switch_screen(self):
        """Метод, который вызывается при тапе по виджету героя."""

        # Устанавливает имя текущего героя для экранного менеджера.
        self.manager.current_hero = self.ids.hero.tag
        # Переключаем экран.
        self.manager.current = "second screen"

После вышеприведенных преобразований при тапе по виджету героя имеем следующее поведение:


Для обратной анимации вращения героя использует метод on_transform_out:

    def on_transform_out(self, instance_hero_widget, instance_image, duration):
        Animation(rotate_value_angle=0, duration=duration).start(
            instance_image
        )

<Hero>
    [...]

    MDHeroFrom:
        [...]

        HeroImage:
            [...]
            on_transform_in: root.on_transform_in(*args)
            on_transform_out: root.on_transform_out(*args)

    MDLabel:
        [...]



На этом сегодняшний короткий обзор закончен. Спасибо за внимание!

Комментарии (0)