Русский
Русский
English
Статистика
Реклама

Материальный дизайн. Создание анимаций в Kivy



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

Чтобы подогреть интерес читателей, я хочу сразу показать результат того, что у нас получится в итоге:



Итак, для работы нам понадобится фреймворк Kivy:

pip install kivy

И библиотека KivyMD, которая предоставляет виджеты в стиле Material Design для фреймворка Kivy:

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

Все готово к работе! Откроем PyCharm и создадим новый проект CallScreen со следующей структурой катологов:



Структура может любая. Ни фреймворк Kivy, ни библиотека KivyMD не требует никаких обязательных директорий, кроме стандартного требования в корне проекта должен быть файл с именем main.py. Это точка входа в приложение:



В каталоге data/images я разместил графические ресурсы, которые требуются приложению:

В директории uix/screens/baseclass у нас будет размещаться файл callscreen.py с одноименным Python классом, в котором мы будем реализовывать логику работы экрана приложения:



А в директории uix/screens/kv мы создадим файл callscreen.kv (пока оставим пустым) с описанием UI на специальном DSL языке Kivy Language:



Когда проект создан, мы можем открыть файл callscreen.py и реализовать класс экрана нашего тестового приложения.

callscreen.py:

import osfrom kivy.lang import Builderfrom kivymd.uix.screen import MDScreen# Читаем и загружаем KV файлwith open(os.path.join(os.getcwd(), "uix", "screens", "kv", "callscreen.kv"), encoding="utf-8") as KV:    Builder.load_string(KV.read())class CallScreen(MDScreen):    pass


Класс CallScreen унаследован от виджета MDScreen библиотеки KivyMD (почти все компоненты этой библиотеки имеют префикс MD Material Design). MDScreen это аналог виджета Screen фреймворка Kivy из модуля kivy.uix.screenmanager, но с дополнительными свойствами. Также MDScreen позволяет размещать в себе виджеты и контроллы один над другим следующим образом:


Именно это позиционирование мы будем использовать, размещая плавающие элементы на экране.

В точке входа в приложение файл main.py создадим класс TestCallScreen, унаследованный от класса MDApp с обязательным методом build, который должен возвращать виджет или лайоут, для отображения его на экране. В нашем случае это будет созданный ранее класс экрана CallScreen.

main.py:

from kivymd.app import MDAppfrom uix.screens.baseclass.callscreen import CallScreenclass TestCallScreen(MDApp):    def build(self):        return CallScreen()TestCallScreen().run()


Это уже готовое приложение, которое отображает пустой экран. Если запустить файл main.py, увидим:



Теперь приступим к разметке UI экрана в файле callscreen.kv. Для этого нужно создать одноименное с базовым классом правило, в котором мы будем описывать виджеты и их свойства. Например, если у нас есть Python класс c именем CallScreen, то и правило в KV файле должно иметь точно такое же имя. Хотя вы можете создавать все элементы интерфейса прямо в коде, но это, мягко говоря, не правильно. Сравните:

MyRootWidget:    BoxLayout:        Button:        Button:

И аналог на Python:

root = MyRootWidget()box = BoxLayout()box.add_widget(Button())box.add_widget(Button())root.add_widget(box)

Совершенно очевидно, что дерево виджетов намного читабельнее в Kv Language, чем в Python коде. К тому же, когда появятся аргументы у виджетов, ваш Python код станет просто сплошной кашей и уже через день вы не сможете разобраться в нем. Поэтому кто бы что ни говорил, но если фреймворк позволяет описывать элементы UI посредством декларативного языка, это плюс. Ну, а в Kivy это двойной плюс, потому что в Kv Language еще можно выполнять инструкции Python.

Итак, начнем, пожалуй, с титульного изображения:

callscreen.kv:

<CallScreen>    FitImage:        id: title_image  # id для обращения к данному виджету        size_hint_y: .45  # высота изображения (45% от высоты экрана)        # Идентификатор root всегда ссылается на базовый класс.        # В нашем случае это <class 'uix.screens.baseclass.callscreen.CallScreen'>,        # а self - объект самого виджета - <kivymd.utils.fitimage.FitImage object>.        y: root.height - self.height  # положение по оси Y        source: "data/images/avatar.jpg"  # путь к изображению


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


Можем запустить файл main.py и посмотреть результат:



Пока все просто и самое время приступить к анимированию виджетов. Добавим кнопку в экран по нажатию которой будут вызываться методы анимации из Python класса CallScreen:

callscreen.kv:

#:import get_color_from_hex kivy.utils.get_color_from_hex#:import colors kivymd.color_definitions.colors<CallScreen>    FitImage:        [...]    MDFloatingActionButton:        icon: "phone"        x: root.width - self.width - dp(20)        y: app.root.height * 45 / 100 + self.height / 2        md_bg_color: get_color_from_hex(colors["Green"]["A700"])        on_release:            # Вызов метода анимации титульного изображения.            root.animation_title_image(title_image); \            root.open_call_box = True if not root.open_call_box else False

Импорты модулей в Kv Language:

#:import get_color_from_hex kivy.utils.get_color_from_hex#:import colors kivymd.color_definitions.colors

Будут аналогичны следующим импортам в Python коде:

# Метод get_color_from_hex нужен дляпреобразования цвета# из шестнадцатеричной строки в формат rgba.from kivy.utils import get_color_from_hex# Словарь оттенков цветов различных цветовых схем:## colors = {#     "Red": {#         "50": "FFEBEE",#         "100": "FFCDD2",#         ...,#     },#     "Pink": {#         "50": "FCE4EC",#         "100": "F8BBD0",#         ...,#     },#     ...# }## https://kivymd.readthedocs.io/en/latest/themes/color-definitions/from kivymd.color_definitions import colors


После запуска и нажатия на зеленую кнопку получим AttributeError: 'CallScreen' object has no attribute 'animation_title_image'. Поэтому вернемся к базовому классу CallScreen в файле callscreen.py и создадим в нем метод animation_title_image, в котором будем анимировать титульное изображение.

callscreen.py:

# Класс для анимирования свойств виджетов.from kivy.animation import Animation[...]class CallScreen(MDScreen):    # Флаг для анимации возврата экрана к исходному состоянию.    open_call_box = False    def animation_title_image(self, title_image):        """        :type title_image: <kivymd.utils.fitimage.FitImage object>        """        if not self.open_call_box:            # Анимация развертывания титульного изображения на весь экран.            Animation(size_hint_y=1, d=0.6, t="in_out_quad").start(title_image)        else:            # Анимация возврата титульного изображения к исходному состоянию.            Animation(size_hint_y=0.45, d=0.6, t="in_out_quad").start(title_image)

Как вы уже поняли, класс Animation, наверное, как и в других фреймворках, просто анимирует свойство виджета. В нашем случае мы анимируем свойство size_hint_y подсказка высоты, задавая интервал выполнения анимации в параметре d duration и тип анимации в параметре t type. Мы можем анимировать сразу несколько свойств одного виджета, комбинировать анимации с помощью операторов +, += На изображении ниже показан результат нашей работы. Для сравнения для правой гифки я использовал типы анимаций in_elastic и out_elastic:

Следующий наш шаг добавить blur эффект к титульному изображению. Для этих целей в Kivy существует EffectWidget. Нам нужно установить нужные свойства для эффекта и поместить виджет титульного изображения в EffectWidget.

callscreen.kv:

#:import effect kivy.uix.effectwidget.EffectWidget#:import HorizontalBlurEffect kivy.uix.effectwidget.HorizontalBlurEffect#:import VerticalBlurEffect kivy.uix.effectwidget.VerticalBlurEffect#:import get_color_from_hex kivy.utils.get_color_from_hex#:import colors kivymd.color_definitions.colors<CallScreen>    EffectWidget:        effects:            # blur_value значение степени размытия.            (\            HorizontalBlurEffect(size=root.blur_value), \            VerticalBlurEffect(size=root.blur_value), \            )        FitImage:            [...]    MDFloatingActionButton:        [...]        on_release:            # Вызов метода анимации blur эффекта.            root.animation_blur_value(); \            [...]

Теперь нужно добавить атрибут blur_value в базовый класс Python CallScreen и создать метод animation_blur_value, который будет анимировать значение эффекта размытия.

callscreen.py:

from kivy.properties import NumericProperty[...]class CallScreen(MDScreen):    # Значение степени размытия для EffectWidget.    blur_value = NumericProperty(0)    [...]    def animation_blur_value(self):        if not self.open_call_box:            Animation(blur_value=15, d=0.6, t="in_out_quad").start(self)        else:            Animation(blur_value=0, d=0.6, t="in_out_quad").start(self)

Результат:


Обратите внимание, что методы анимирования будут выполнятся асинхронно! Давайте анимируем зеленую кнопку вызова, чтобы она не мозолила нам глаза.

callscreen.py:

from kivy.utils import get_color_from_hexfrom kivy.core.window import Windowfrom kivymd.color_definitions import colors[...]class CallScreen(MDScreen):    [...]    def animation_call_button(self, call_button):        if not self.open_call_box:            Animation(                x=self.center_x - call_button.width / 2,                y=dp(40),                md_bg_color=get_color_from_hex(colors["Red"]["A700"]),                d=0.6,                t="in_out_quad",            ).start(call_button)        else:            Animation(                y=Window.height * 45 / 100 + call_button.height / 2,                x=self.width - call_button.width - dp(20),                md_bg_color=get_color_from_hex(colors["Green"]["A700"]),                d=0.6,                t="in_out_quad",            ).start(call_button)


callscreen.kv:

[...]<CallScreen>    EffectWidget:        [...]        FitImage:            [...]    MDFloatingActionButton:        [...]        on_release:            # Вызов метода анимации кнопки вызова.            root.animation_call_button(self); \            [...]



Добавим два пункиа типа TwoLineAvatarListItem на главный экран.

callscreen.kv:

#:import STANDARD_INCREMENT kivymd.material_resources.STANDARD_INCREMENT#:import IconLeftWidget kivymd.uix.list.IconLeftWidget[...]<ItemList@TwoLineAvatarListItem>    icon: ""    font_style: "Caption"    secondary_font_style: "Caption"    height: STANDARD_INCREMENT    IconLeftWidget:        icon: root.icon<CallScreen>    EffectWidget:        [...]        FitImage:            [...]    MDBoxLayout:        id: list_box        orientation: "vertical"        adaptive_height: True        y: root.height * 45 / 100 - self.height / 2        ItemList:            icon: "phone"            text: "Phone"            secondary_text: "123 456 789"        ItemList:            icon: "mail"            text: "Email"            secondary_text: "kivydevelopment@gmail.com"    MDFloatingActionButton:        [...]        on_release:            root.animation_list_box(list_box); \            [...]



Мы создали два пункта ItemList и разместили их в вертикальном боксе. Можем создать новый метод animation_list_box в классе CallScreen для анимации этого бокса.

callscreen.py:

[...]class CallScreen(MDScreen):    [...]    def animation_list_box(self, list_box):        if not self.open_call_box:            Animation(                y=-list_box.y,                opacity=0,                d=0.6,                t="in_out_quad"б            ).start(list_box)        else:            Animation(                y=self.height * 45 / 100 - list_box.height / 2,                opacity=1,                d=0.6,                t="in_out_quad",            ).start(list_box)



Добавим панель инструментов в экран.

callscreen.kv:

[...]<CallScreen>    EffectWidget:        [...]        FitImage:            [...]    MDToolbar:        y: root.height - self.height - dp(20)        md_bg_color: 0, 0, 0, 0        opposite_colors: True        title: "Profile"        left_action_items:  [["menu", lambda x: x]]        right_action_items: [["dots-vertical", lambda x: x]]    MDBoxLayout:        [...]        ItemList:            [...]        ItemList:            [...]    MDFloatingActionButton:        [...]



Аватар и имя пользователя.

callscreen.kv:

[...]<CallScreen>    EffectWidget:        [...]        FitImage:            [...]    MDToolbar:        [...]    MDFloatLayout:        id: round_avatar        size_hint: None, None        size: "105dp", "105dp"        md_bg_color: 1, 1, 1, 1        radius: [self.height / 2,]        y: root.height * 45 / 100 + self.height        x: root.center_x - (self.width + user_name.width + dp(20)) / 2        FitImage:            size_hint: None, None            size: "100dp", "100dp"            mipmap: True            source: "data/images/round-avatar.jpg"            radius: [self.height / 2,]            pos_hint: {"center_x": .5, "center_y": .5}            mipmap: True    MDLabel:        id: user_name        text: "Irene"        font_style: "H3"        bold: True        size_hint: None, None        -text_size: None, None        size: self.texture_size        theme_text_color: "Custom"        text_color: 1, 1, 1, 1        y: round_avatar.y + self.height / 2        x: round_avatar.x + round_avatar.width + dp(20)    MDBoxLayout:        [...]        ItemList:            [...]        ItemList:            [...]    MDFloatingActionButton:        root.animation_round_avatar(round_avatar, user_name); \        root.animation_user_name(round_avatar, user_name); \        [...]



Типичное анимирование позиций X и Y аватара и имени пользователя.

callscreen.py:

[...]class CallScreen(MDScreen):    [...]    def animation_round_avatar(self, round_avatar, user_name):        if not self.open_call_box:            Animation(                x=self.center_x - round_avatar.width / 2,                y=round_avatar.y + dp(50),                d=0.6,                t="in_out_quad",            ).start(round_avatar)        else:            Animation(                x=self.center_x - (round_avatar.width + user_name.width + dp(20)) / 2,                y=self.height * 45 / 100 + round_avatar.height,                d=0.6,                t="in_out_quad",            ).start(round_avatar)    def animation_user_name(self, round_avatar, user_name):        if not self.open_call_box:            Animation(                x=self.center_x - user_name.width / 2,                y=user_name.y - STANDARD_INCREMENT,                d=0.6,                t="in_out_quad",            ).start(self.ids.user_name)        else:            Animation(                x=round_avatar.x + STANDARD_INCREMENT,                y=round_avatar.center_y - user_name.height - dp(20),                d=0.6,                t="in_out_quad",            ).start(user_name)



Нам осталось создать бокс с кнопками:



На момент написания статьи я столкнулся с тем, что в библиотеке KivyMD не обнаружилось нужной кнопки. Пришлось по-быстрому смастерить её самому. Я просто добавил в существующий класс MDIconButton инструкции canvas, в которых определил окружность вокруг кнопки, и поместил ее вместе с меткой в вертикальный бокс.

callscreen.kv:

<CallBoxButton@MDBoxLayout>    orientation: "vertical"    adaptive_size: True    spacing: "8dp"    icon: ""    text: ""    MDIconButton:        icon: root.icon        theme_text_color: "Custom"        text_color: 1, 1, 1, 1        canvas:            Color:                rgba: 1, 1, 1, 1            Line:                width: 1                circle:                    (\                    self.center_x, \                    self.center_y, \                    min(self.width, self.height) / 2, \                    0, \                    360, \                    )    MDLabel:        text: root.text        size_hint_y: None        height: self.texture_size[1]        font_style: "Caption"        halign: "center"        theme_text_color: "Custom"        text_color: 1, 1, 1, 1[...]



Далее мы создаем бокс для размещения кастомных кнопок.

callscreen.kv:

<CallBox@MDGridLayout>    cols: 3    rows: 2    adaptive_size: True    spacing: "24dp"    CallBoxButton:        icon: "microphone-off"        text: "Mute"    CallBoxButton:        icon: "volume-high"        text: "Speaker"    CallBoxButton:        icon: "dialpad"        text: "Keypad"    CallBoxButton:        icon: "plus-circle"        text: "Add call"    CallBoxButton:        icon: "call-missed"        text: "Transfer"    CallBoxButton:        icon: "account"        text: "Contact"[...]



Теперь созданный CallBox размещаем в правиле CallScreen и устанавливаем его положение по оси Y за нижней границей экрана.

callscreen.kv:

[...]<CallScreen>    EffectWidget:        [...]        FitImage:            [...]    MDToolbar:        [...]    MDFloatLayout:        [...]        FitImage:            [...]    MDLabel:        [...]    MDBoxLayout:        [...]        ItemList:            [...]        ItemList:            [...]    MDFloatingActionButton:        root.animation_call_box(call_box, user_name); \        [...]    CallBox:        id: call_box        pos_hint: {"center_x": .5}        y: -self.height        opacity: 0


Остается только анимировать положение созданного бокса с кнопками.

callscreen.py:

from kivy.metrics import dp[...]class CallScreen(MDScreen):    [...]    def animation_call_box(self, call_box, user_name):        if not self.open_call_box:            Animation(                y=user_name.y - call_box.height - dp(100),                opacity=1,                d=0.6,                t="in_out_quad",            ).start(call_box)        else:            Animation(                y=-call_box.height,                opacity=0,                d=0.6,                t="in_out_quad",            ).start(call_box)



Финальная GIF-ка с тестом на мобильном устройстве:



На этом все, надеюсь, был полезен!
Источник: habr.com
К списку статей
Опубликовано: 15.11.2020 22:16:27
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

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

Разработка под macos

Разработка под linux

Разработка под windows

Kivy

Kivymd

Python

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru