Приветствую вас, дорогие любители и знатоки 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 вы можете прочитать в статье Python GUI. Библиотека KivyMD. Шаблон MVC, parallax эффект и анимация контента слайдов
Открываем проект, и удаляем, кроме имя правила, все содержимое файла blur_concept_screen.kv - это разметка нашего единственного экрана в приложении:
Посмотрим на готовый экран приложения и прикинем какие элементы UI мы можем переиспользовать:
Я выделил метки и два текстовых поля. В проекте в каждой директории представлений для каждого экрана (View) есть пакет components:
Создадим два пакета в директории 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
Теперь откроем в файл с классом представления экрана:
... и объявим необходимые константы и методы:
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 изображения, которые пошагово демонстрируют добавление всех элементов, потому что новый редактор Хабра просто наотрез отказался грузить какие-либо изображения после последней гифки в статье. Обидно, но ладно. Всем спасибо за внимание и до новых встреч!
Комментарии (11)
Tuwogaka
01.11.2021 23:43На десятый питон ставится? А то на сайте только 3.9.
KivyMD Автор
02.11.2021 00:19Пока поддержка Python 3.10 вроде как в разработке у Kivy еще.
Tuwogaka
02.11.2021 11:30Поскольку моя карма снижается даже если я ничего не делаю, то можно и лишний раз выразить своё мнение, пока ещё данный ресурс позволяет это проделать несмотря на явную заточку под промывку сознания.
Десятый питон что, выпускался под покровом тайны и под охраной ЧВК, или Kivy сознательно культивирует культуру занятия интересным в ущерб неинтересному, сколь бы нужным таковое ни было? Судя по тому, как знакомство с Kivy начинается с настоятельнейших рекомендаций установить виртуальное окружение питона - второе.
Поэтому речи про использование одной библиотеки поверх другой - весьма интересно, но при наличии малейшего шанса что своё творение придётся поддерживать сколь либо заметный срок - строго теоретически интересно. Хотя получить очередное приглашение в мир Открытого Софта со товарищи - весьма приятно, хотя бы как возможность полюбоваться трагической красотой смеси пока не отлаженного с уже устаревшим.
Недавно Rust выпустил издание 2021 с документацией от издания 2018, а близкие к минимальным изменения между изданиями документировал отдельно. Объяснить, что если изменения минимальны, то документация должна была быть обновлена непременно, а если изменения значительны, то документация тем более должна была быть обновлена непременно, на хабре оказалось невозможно, культура такая уж сложилась вот.
Вот примерно так видится со стороны. Кто первый переломит складывающуюся культуру - того будут и плюшки.
stalker1984
02.11.2021 08:49Это конечно наверно на вкус и цвет, но у вас верхние блоки по левому краю выравнены а нижние по центру. Ну и "фогот пассворд" наверное тоже стоило с засечками сделать. В общем стоит поработать со шрифтами.
kAIST
Спасибо за статью. А теперь вопрос:
Можно ли сделать наоборот, чтобы блюра не было? ;)
Сейчас мне нужно написать кое какие приложение, для простоты назовем его фотогалереей. Третий день мучаю kivy и kivymd. Так вот, основная проблема это windows с масштабом интерфейса, отличным от 100%. Приложения kivy выглядят размыто. Есть ли решение проблемы?
KivyMD Автор
Не совсем понял, зачем делать масштаб интерфейса, отличным от 100%
kAIST
У меня есть ноутбук 14 дюймов с разрешением FullHD, есть планшет на windows с разрешением 1920х1200. При 100% интерфейс будет очень мелкий. Даже сама windows "из коробки" ставит 150%.
KivyMD Автор
Нужно использовать метрики для шрифтов и размеров элементов интерфейса. Также можно попытаться изменить значение KIVY_DPI_DENSITY. Я с такой проблемой ни сталкивался во всяком случае.
kAIST
Поискал по issues, проблема оказывается много лет. Это камень не в ваш огород, а в огород kivy, потому что масштаб интерфейса отличный от 100%, это обычное дело в 2021 году и даже отсталый tkinter скейлится нормально (ручками правда нужно пару строк написать).
KivyMD Автор
Думаю, скоро поправят.