Материальный Python. Кастомные карточки с OpenGL эффектами

—

от автора

Приветствую, уважаемые любители и знатоки 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. В конце статьи оставляю опрос, в котором вам нужно угадать, какая технология и где используется.

ссылка на оригинал статьи https://habr.com/ru/post/502438/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *