
Приветствую, уважаемые любители и знатоки Python!
В этой статье я покажу вам, как применять эффекты OpenGL к своим кастомным карточкам, если вы используете в своих приложениях такие кроссплатформенные инструменты как фреймворк Kivy и библиотеку материального дизайна для этого фреймворка — KivyMD. Погнали!
В KivyMD есть стандартный компонент MDCard — базовый класс для создания различных пользовательских карточек (Material Design spec, Cards). Если не вдаваться в подробности, то под капотом MDCard находится обычный BoxLayout — контейнер, который позволяет размещать в себе другие виджеты в вертикальной или горизонтальной ориентации. То есть, если вам понадобилось сделать какую-нибудь карточку, например, информацию о пользователе, вы делаете это самостоятельно. MDCard реализует только поведения ripple_behavior, touch_behavior и отбрасываемую тень:
![]() |
![]() |
Пример программы которая выводит на экран пустую карту выглядит следующим образом:
from kivy.lang import Builder from kivymd.app import MDApp KV = ''' Screen: # экран приложения MDCard: # карта # подсказки размера и позиции size_hint: .6, .5 pos_hint: {"center_x": .5, "center_y": .5} ''' class TestCard(MDApp): def build(self): return Builder.load_string(KV) TestCard().run()
Результат:

Выглядит довольно просто. Но что делать, если мы хотим красивую карточку с Blur эффектом при событии получения фокуса? Такую как, например, в приложении Flutter UI Designs:

Придется сделать ее самому! Тем более, что ничего сложного в этом нет. Для начала создадим базовый класс будущей карты:
class RestaurantCard(MDCard): source = StringProperty() # путь к главному изображению карточки shadow = StringProperty() # путь к нижнему изображению-тени text = StringProperty() # текст карточки
Главное изображение карточки:

Изображение-тень:

Теперь наполним карту компонентами, свойства которых мы определи при помощи специального DSL языка Kv-Language, предназначенного для удобного проектирования макетов интерфейса:
<RestaurantCard> elevation: 12 RelativeLayout: # Компонент, который подгонят пропорции изображения под размеры макета. FitImage: # главное изображение карточки source: root.source FitImage: # изображение-тень source: root.shadow size_hint_y: None height: "120dp" MDLabel: # текст карточки text: root.text markup: True size_hint_y: None height: self.texture_size[1] x: "10dp" y: "10dp" theme_text_color: "Custom" text_color: 1, 1, 1, 1
В карту мы поместили виджет RelativeLayout, который позволяет размешать в себе компоненты один над другим вот таким образом:

Мы же разместили сначала главное изображение, сверху положили тень и текст. Теперь если запустить наш код:
from kivy.lang import Builder from kivy.properties import StringProperty from kivymd.app import MDApp from kivymd.uix.card import MDCard KV = """ <RestaurantCard> elevation: 12 RelativeLayout: FitImage: source: root.source FitImage: source: root.shadow size_hint_y: None height: "120dp" MDLabel: text: root.text markup: True size_hint_y: None height: self.texture_size[1] x: "10dp" y: "10dp" theme_text_color: "Custom" text_color: 1, 1, 1, 1 Screen: RestaurantCard: text: "[size=23][b]Restaurant[/b][/size]\\nTuborg Havnepark 15, Hellerup 2900 Denmark" shadow: "shadow-black.png" source: "restourant.jpg" pos_hint: {"center_x": .5, "center_y": .5} size_hint: .7, .5 """ class RestaurantCard(MDCard): source = StringProperty() text = StringProperty() shadow = StringProperty() class BlurCard(MDApp): def build(self): return Builder.load_string(KV) BlurCard().run()
… получим результат:

И результат, конечно, далек от ожидаемого, потому что ни blur эффекта, ни заругленных краев у карточки мы не увидим. Начнем с blur эффекта. В Kivy есть стандартный виджет EffectWidget, который способен применять различные графические эффекты для своих детей. Он работает путем рендеринга Fbo экземпляров с помощью пользовательских шейдеров OpenGL. Нам нужно применить эффект размытия к главному изображению и изображению-тени на карточке. Поэтому мы должны поместить их компоненты в виджет EffectWidget:
#:import effect kivy.uix.effectwidget.EffectWidget #:import HorizontalBlurEffect kivy.uix.effectwidget.HorizontalBlurEffect <RestaurantCard> ... RelativeLayout: # Здесь находятся компоненты, к которым будет применён выбранный эффект. EffectWidget: # Тип эффекта. effects: (HorizontalBlurEffect(size=root.blur),) FitImage: source: root.source FitImage: source: root.shadow size_hint_y: None height: "120dp" ...
Добавим поле для значения степени эффекта размытия:
class RestaurantCard(MDCard): ... blur = NumericProperty(8)
Запускаем и видим:

При наведении курсора (если это десктоп) или при тапе (если это mobile) ничего не происходит. Чтобы карточка реагировала на событие on_focus, мы должны включить чтение этого события в свойствах правила RestaurantCard и назначить методы, которые будут выполняться при регистрации этого собития:
#:import Animation kivy.animation.Animation <RestaurantCard> focus_behavior: True # включаем чтение события on_focus # Методы, которые вызываются при захвате и потере фокуса. # Используя класс Animation, меняем значение степени размытия. on_enter: Animation(blur=0, d=0.3).start(self) on_leave: Animation(blur=8, d=0.3).start(self)
Уже лучше:

Для обрезания углов у карточки я решил применить Stencil (трафарет) к виджету EffectWidget:
#:import Stencil kivymd.uix.graphics.Stencil # Создаем новое правило, унаследованное от EffectWidget и Stencil. <Effect@EffectWidget+Stencil> radius: [20,] <RestaurantCard> ... RelativeLayout: Effect: ...
И вот теперь все работает так, как мы планировали:

Полный код примера
from kivy.lang import Builder from kivy.properties import StringProperty, NumericProperty from kivymd.app import MDApp from kivymd.uix.card import MDCard KV = """ #:import Stencil kivymd.uix.graphics.Stencil #:import Animation kivy.animation.Animation #:import effect kivy.uix.effectwidget.EffectWidget #:import HorizontalBlurEffect kivy.uix.effectwidget.HorizontalBlurEffect <Effect@EffectWidget+Stencil> radius: [20,] <RestaurantCard> md_bg_color: 0, 0, 0, 0 elevation: 12 focus_behavior: True on_enter: Animation(blur=0, d=0.3).start(self) on_leave: Animation(blur=8, d=0.3).start(self) radius: [20,] RelativeLayout: Effect: effects: (HorizontalBlurEffect(size=root.blur),) FitImage: source: root.source FitImage: source: root.shadow size_hint_y: None height: "120dp" MDLabel: text: root.text markup: True size_hint_y: None height: self.texture_size[1] x: "10dp" y: "10dp" theme_text_color: "Custom" text_color: 1, 1, 1, 1 FloatLayout: RestaurantCard: text: "[size=23][b]Restaurant[/b][/size]\\nTuborg Havnepark 15, Hellerup 2900 Denmark" shadow: "shadow-black.png" source: "restourant.jpg" pos_hint: {"center_x": .5, "center_y": .5} size_hint: .7, .5 """ class RestaurantCard(MDCard): source = StringProperty() text = StringProperty() shadow = StringProperty() blur = NumericProperty(8) class BlurCard(MDApp): def build(self): return Builder.load_string(KV) BlurCard().run()
Ну, и напоследок хочу показать видео, в котором работают две программы: Одна, написанная с использованием фреймворка Flutter, а вторая — c использованием Kivy и KivyMD. В конце статьи оставляю опрос, в котором вам нужно угадать, какая технология и где используется.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Flutter приложение находится:
59.52%В левом углу25
40.48%В правом углу17
Проголосовали 42 пользователя. Воздержались 16 пользователей.


