Как стать автором
Обновить

Python GUI. Библиотека KivyMD. Использование Blur эффекта

Время на прочтение8 мин
Количество просмотров14K

Приветствую вас, дорогие любители и знатоки Python! Сегодня как всегда будем делать обзор фреймворка для кроссплатформенной разработки Kivy и библиотеки виджетов в стиле Google Material Design для фреймворка Kivy - KivyMD. В этой статье мы рассмотрим как сделать blur эффект отдельных компонентов интерфейса пользователя. Я уже делал похожий обзор в статье Материальный Python. Кастомные карточки с OpenGL-эффектами, но нижеследующий материал несколько сложнее и в прямом смысле динамичнее. Что ж, начнем...

А для тех, кто незнаком ни с 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 библиотека не используется. Текущее состояние - бета. Мастер версия на GitHub - 1.0.0

Два этих проекта развиваются отдельно друг от друга, но поскольку из коробки Kivy имеет стандартный "убогий" дизайн виджетов, а современные приложения не приемлют серости и всем хочется юзать красивые красивости, люди часто путают Kivy и KivyMD, задавая, например, вопросы типа "Как мне скомпилировать для моего приложения KivyMD вместо Kivy?", что, конечно же, некорректно, поскольку KivyMD это всего лишь дополнительные виджеты для фреймворка Kivy, реализованные с помощью него же. Эти виджеты никогда не будут включены в стандартную библиотеку Kivy и не заменят собой страшные дефолтные виджеты Kivy, потому что Kivy хочет быть кроссплатформенным на 100% и не привязывается ни к материальному дизайну ни к какому-либо еще, оставляя все на стороне пользователя: хочется дизайн как у Mac OS - делайте как у Mac OS, хочется дизайн как в Windows - делайте как в Windows, благо все виджеты Kivy кастомизируются легко и быстро.

Ну а наша сегодняшняя задача - сделать нижеследующий концепт экрана:

Основная цель - blur эффект для двух текстовых полей. Анимированный фон я сделал для того, чтобы сделать акцент на динамике. Итак, для работы нам понадобится Python 3.6 - 3.9 (3.10 пока в разработке у Kivy), фреймворк Kivy и библиотека KivyMD. Установим все это дело:

pip install "kivy[base] @ https://github.com/kivy/kivy/archive/master.zip"
pip install https://github.com/kivymd/KivyMD/archive/master.zip

Первая команда установит фреймворк Kivy мастер версии, а вторая - библиотеку KivyMD также мастер версии. Все. Можем работать. Откроем консоль и создадим шаблон проекта с помощью инструментов KivyMD:

python3.9 -m kivymd.tools.patterns.create_project \
    MVC \
    /Users/macbookair \
    BlurConcept \
    python3.9 \
    master \
    --name_screen BlurConceptScreen

Будет создан проект с шаблоном MVC в директории /Users/macbookair с именем BlurConcept, виртуальным окружением и всеми необходимыми зависимостями:

Базовый шаблон проекта KivyMD
Базовый шаблон проекта KivyMD

Детально об архитектуре проекта, которую предлагает использовать KivyMD вы можете прочитать в статье Python GUI. Библиотека KivyMD. Шаблон MVC, parallax эффект и анимация контента слайдов

Открываем проект, и удаляем, кроме имя правила, все содержимое файла blur_concept_screen.kv - это разметка нашего единственного экрана в приложении:

blur_concept_screen.kv
blur_concept_screen.kv

Посмотрим на готовый экран приложения и прикинем какие элементы UI мы можем переиспользовать:

Я выделил метки и два текстовых поля. В проекте в каждой директории представлений для каждого экрана (View) есть пакет components:

components для экрана BlurConceptScreenView
components для экрана BlurConceptScreenView

Создадим два пакета в директории components для экрана BlurConceptScreenView - commonlabel и fillfield - метку и текстовое поле с общими параметрами:

commonlabel.py:

from kivymd.uix.label import MDLabel


class CommonLabel(MDLabel):
    """It is just a base class for a label with common parameters."""

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.color = (1, 1, 1, 1)
        self.font_style = "H4"
        self.font_name = "assets/fonts/PlayfairDisplay-Black.otf"
        self.adaptive_height = True
        self.markup = True

fillfield.py:

from kivymd.uix.textfield import MDTextField


class FillField(MDTextField):
    """It is just a base class for a text field with common parameters."""

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.size_hint_x = None
        self.width = "320dp"
        self.mode = "fill"
        self.fill_color_normal = (1, 1, 1, 0.1)
        self.fill_color_focus = (1, 1, 1, 0.3)

В файле __init__.py сделаем импорт этих классов, чтобы в дальше их можно было импортировать непосредственно из пакета components:

components/__init__.py:

from .fillfield.fill_field import FillField
from .commonlabel.common_label import CommonLabel

Теперь откроем в файл с классом представления экрана:

blur_concept_screen.py
blur_concept_screen.py

... и объявим необходимые константы и методы:

from kivy.animation import Animation
from kivy.metrics import dp
from kivy.properties import NumericProperty

from View.base_screen import BaseScreenView
from View.BlurConceptScreen.components import FillField, CommonLabel


class BlurConceptScreenView(BaseScreenView):
    """Implements the login start screen in the user application."""

    OPACITY = NumericProperty(0)
    SHIFT_Y = NumericProperty(dp(0))
    FIELD_WIDTH = NumericProperty(dp(320))
    FIELD_HEIGHT = NumericProperty(dp(52))
    PADDING = NumericProperty(dp(24))

    def on_enter(self, *args):
        """
        Event called when the screen is displayed: the entering animation is
        complete.
        """

        animation = Animation(SHIFT_Y=dp(140), d=2, t="in_out_quart")
        animation.bind(on_complete=self.animation_bg_zoom)
        animation.start(self)
        Animation(OPACITY=1, d=3).start(self)

    def animation_bg_zoom(self, *args):
        Animation(height=self.ids.bg.height + self.SHIFT_Y, d=2, t="in_out_quart").start(
            self.ids.bg
        )

Алгоритм прост: в момент появления экрана в методе on_enter с помощью класса Animation мы будет анимировать два поля класса BlurConceptScreen: OPACITY и SHIFT_Y - прозрачность меток экрана и сдвиг меток по оси Y. Ну, а изменения свойств базового класса в одноименном правиле разметки в KV файле применяются автоматически:

blur_concept_screen.kv:

#:import STANDARD_INCREMENT kivymd.material_resources.STANDARD_INCREMENT


<BlurConceptScreenView>

    FitImage:
        id: bg
        size_hint_y: None
        source: "assets/images/bg.jpg"
        height: root.height + root.SHIFT_Y

    CommonLabel:
        x: field_login.x
        y: root.height - root.SHIFT_Y
        opacity: root.OPACITY
        text: "You are\nUp Above the Sky"

    CommonLabel:
        x: field_login.x
        opacity: root.OPACITY
        font_name: "assets/fonts/PlayfairDisplay-Regular.otf"
        text: "Get Started Here"
        y: (field_login.y + root.SHIFT_Y) - (root.PADDING + self.height)

    FillField:
        id: field_login
        opacity: root.OPACITY
        hint_text: "Login"
        icon_left: "account"
        pos:
            root.center[0] - root.FIELD_WIDTH / 2, \
            root.center[1] + root.SHIFT_Y / 4

    FillField:
        id: field_password
        opacity: root.OPACITY
        hint_text: "Password"
        icon_left: "lock"
        pos:
            root.center[0] - root.FIELD_WIDTH / 2, \
            root.center[1] - root.SHIFT_Y / 4

    CommonLabel:
        id: lbl_forgot_password
        opacity: root.OPACITY
        font_style: "Caption"
        halign: "center"
        y: field_password.y - root.PADDING * 2
        text: "Forgot Password?"

    MDFillRoundFlatButton:
        id: sign_in_button
        text: "Sign In"
        opacity: root.OPACITY
        size_hint_x: None
        -width: field_password.width
        pos_hint: {"center_x": .5}
        font_size: "24sp"
        font_name: "assets/fonts/PlayfairDisplay-Black.otf"
        md_bg_color: .8, .8, .8, 1
        text_color: app.theme_cls.primary_color
        ripple_color: app.theme_cls.primary_color[:-1] + [.4]
        y: root.SHIFT_Y

    CommonLabel:
        opacity: root.OPACITY
        font_style: "Caption"
        font_name: "assets/fonts/PlayfairDisplay-Regular.otf"
        halign: "center"
        y: dp(160) - root.SHIFT_Y
        text:
            "Don`t have an Account? " \
            "[font=assets/fonts/PlayfairDisplay-Black.otf]Sign Up[/font]"

Уже можем запустить наш проект и увидеть следующую картину:

Как видим, blur эффекта нет. Для добавления эффекта воспользуемся виджетом EffectWidget:

#:import STANDARD_INCREMENT kivymd.material_resources.STANDARD_INCREMENT
#:import HorizontalBlurEffect kivy.uix.effectwidget.HorizontalBlurEffect
#:import VerticalBlurEffect kivy.uix.effectwidget.VerticalBlurEffect
#:import effect kivy.uix.effectwidget.EffectWidget


<BlurConceptScreenView>

    FitImage:

    CommonLabel:

    CommonLabel:

    EffectWidget:
        opacity: root.OPACITY
        effects:
            ( \
            HorizontalBlurEffect(size=12), \
            VerticalBlurEffect(size=12), \
            )

        canvas.before:
            StencilPush
            RoundedRectangle:
                radius: [10, 10, 0, 0]
                size: root.FIELD_WIDTH, root.FIELD_HEIGHT
                pos:
                    root.center[0] - root.FIELD_WIDTH / 2, \
                    root.center[1] + root.SHIFT_Y / 4
            RoundedRectangle:
                size: root.FIELD_WIDTH, root.FIELD_HEIGHT
                radius: [10, 10, 0, 0]
                pos:
                    root.center[0] - root.FIELD_WIDTH / 2, \
                    root.center[1] - root.SHIFT_Y / 4
            StencilUse

        canvas.after:
            StencilUnUse
            RoundedRectangle:
                size: root.FIELD_WIDTH, root.FIELD_HEIGHT
                radius: [10, 10, 0, 0]
                pos:
                    root.center[0] - root.FIELD_WIDTH / 2, \
                    root.center[1] + root.SHIFT_Y / 4
            RoundedRectangle:
                size: root.FIELD_WIDTH, root.FIELD_HEIGHT
                radius: [10, 10, 0, 0]
                pos:
                    root.center[0] - root.FIELD_WIDTH / 2, \
                    root.center[1] - root.SHIFT_Y / 4
            StencilPop

        FitImage:
            id: bg_blur
            size_hint_y: None
            source: "assets/images/bg.jpg"
            height: root.height + root.SHIFT_Y

    FillField:

    FillField:

    CommonLabel:

    MDFillRoundFlatButton:

    CommonLabel:

EffectWidget способен применять различные графические эффекты к своим потомкам с помощью пользовательских шейдеров и OpenGL. Что мы сделали в нашем конкретном случае, чтобы получить blur эффект? Мы воспользовались инструкциями холста (canvas), определили для них размер и позицию равные размеру и позиции двум текстовым полям, с помощью трафаретов (StencilPush/StencilUse) выделили эти области на холсте и применили blur эффект в свойствах EffectWidget к фоновому изображению:

    EffectWidget:
        effects:
            ( \
            HorizontalBlurEffect(size=12), \
            VerticalBlurEffect(size=12), \
            )

        canvas.before:
            ...

        canvas.after:
            ...

        FitImage:
            id: bg_blur
            size_hint_y: None
            source: "assets/images/bg.jpg"
            height: root.height + root.SHIFT_Y

Теперь нам нужно синхронизировать анимации фоновых изображений, добавив в метод animation_bg_zoom в классе представления анимацию для изображения bg_blur:

class BlurConceptScreenView(BaseScreenView):
    def animation_bg_zoom(self, *args):
        Animation(height=self.ids.bg_blur.height + self.SHIFT_Y, d=2, t="in_out_quart").start(
            self.ids.bg_blur
        )

Если мы в KV разметке в файле blur_concept_screen.kv оставим только EffectWidget с инструкциями холста:

#:import HorizontalBlurEffect kivy.uix.effectwidget.HorizontalBlurEffect
#:import VerticalBlurEffect kivy.uix.effectwidget.VerticalBlurEffect
#:import effect kivy.uix.effectwidget.EffectWidget


<BlurConceptScreenView>

    EffectWidget:
        opacity: root.OPACITY
        effects:
            ( \
            HorizontalBlurEffect(size=12), \
            VerticalBlurEffect(size=12), \
            )

        canvas.before:
            StencilPush
            RoundedRectangle:
                radius: [10, 10, 0, 0]
                size: root.FIELD_WIDTH, root.FIELD_HEIGHT
                pos:
                    root.center[0] - root.FIELD_WIDTH / 2, \
                    root.center[1] + root.SHIFT_Y / 4
            RoundedRectangle:
                size: root.FIELD_WIDTH, root.FIELD_HEIGHT
                radius: [10, 10, 0, 0]
                pos:
                    root.center[0] - root.FIELD_WIDTH / 2, \
                    root.center[1] - root.SHIFT_Y / 4
            StencilUse

        canvas.after:
            StencilUnUse
            RoundedRectangle:
                size: root.FIELD_WIDTH, root.FIELD_HEIGHT
                radius: [10, 10, 0, 0]
                pos:
                    root.center[0] - root.FIELD_WIDTH / 2, \
                    root.center[1] + root.SHIFT_Y / 4
            RoundedRectangle:
                size: root.FIELD_WIDTH, root.FIELD_HEIGHT
                radius: [10, 10, 0, 0]
                pos:
                    root.center[0] - root.FIELD_WIDTH / 2, \
                    root.center[1] - root.SHIFT_Y / 4
            StencilPop

        FitImage:
            id: bg_blur
            size_hint_y: None
            source: "assets/images/bg.jpg"
            height: root.height + root.SHIFT_Y

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

Далее мы просто добавляем все остальные виджеты на экран. К сожалению, я не смог добавить gif изображения, которые пошагово демонстрируют добавление всех элементов, потому что новый редактор Хабра просто наотрез отказался грузить какие-либо изображения после последней гифки в статье. Обидно, но ладно. Всем спасибо за внимание и до новых встреч!

Теги:
Хабы:
+2
Комментарии11

Публикации

Истории

Работа

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн