Python material. Cartões personalizados com efeitos OpenGL


Saudações, queridos amantes e especialistas em Python!

Neste artigo, mostrarei como aplicar efeitos OpenGL a seus cartões personalizados se você usar ferramentas de plataforma cruzada, como a estrutura Kivy e a biblioteca de design de materiais para essa estrutura, KivyMD , em seus aplicativos . Vamos lá!

O KivyMD possui um componente padrão MDCard - a classe base para a criação de vários cartões personalizados ( especificação de projeto de material, cartões ). Se você não entrar em detalhes, então, sob o capô do MDCard está o habitual BoxLayout - um contêiner que permite colocar outros widgets na orientação vertical ou horizontal. Ou seja, se você precisava criar algum tipo de cartão, por exemplo, informações sobre o usuário, faça você mesmo. O MDCard implementa apenas ripple_behavior , touch_behavior e cast shadows:

Um exemplo de programa que exibe um cartão em branco é o seguinte:

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()

Resultado:


Parece bem simples. Mas e se quisermos um cartão bonito com efeito de desfoque no caso de receber o foco? Como, por exemplo, no aplicativo Flutter UI Designs :


Terá que fazer você mesmo! Além disso, não há nada complicado nisso. Primeiro, crie a classe base do mapa futuro:

class RestaurantCard(MDCard):
    source = StringProperty()  #     
    shadow = StringProperty()  #    -
    text = StringProperty()  #  

A imagem principal do cartão:


Imagem de sombra:


Agora, preencheremos o mapa com componentes cujas propriedades determinamos usando a linguagem DSL especial da linguagem Kv , projetada para o design conveniente de layouts de interface:

<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

Colocamos o widget RelativeLayout no mapa , o que nos permite misturar os componentes um acima do outro desta maneira:


Primeiro, colocamos a imagem principal, colocamos uma sombra e um texto em cima. Agora, se executarmos nosso código:

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()

... obtemos o resultado:


E o resultado, é claro, está longe do esperado, porque não veremos nenhum efeito de desfoque ou bordas arredondadas no cartão. Vamos começar com o efeito de desfoque. O Kivy possui um widget EffectWidget padrão que pode aplicar vários efeitos gráficos a seus filhos. Funciona renderizando instâncias do Fbo usando sombreadores OpenGL personalizados. Precisamos aplicar o efeito de desfoque à imagem principal e à imagem de sombra no cartão. Portanto, devemos colocar seus componentes no widget 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"

    ...    

Adicione um campo para o valor do grau do efeito de desfoque:

class RestaurantCard(MDCard):
    ...
    blur = NumericProperty(8)

Começamos e vemos:


Quando você passa o mouse sobre (se for um desktop) ou toca (se for móvel), nada acontece. Para que o cartão responda ao evento on_focus, precisamos habilitar a leitura desse evento nas propriedades da regra RestaurantCard e atribuir métodos que serão executados quando esse evento for registrado:

#: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)

Já melhor:


Para aparar os cantos do cartão, decidi aplicar um estêncil (estêncil) ao widget EffectWidget:

#:import Stencil kivymd.uix.graphics.Stencil


#   ,   EffectWidget  Stencil.
<Effect@EffectWidget+Stencil>
    radius: [20,]


<RestaurantCard>
    ...

    RelativeLayout:

        Effect:
            ...

E agora tudo funciona como planejamos:


Código de exemplo completo
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()


Bem, finalmente, quero mostrar um vídeo no qual dois programas funcionam: um, escrito usando a estrutura Flutter e o segundo - usando o Kivy e o KivyMD. No final do artigo, deixo uma pesquisa na qual você precisa adivinhar qual tecnologia é usada e onde.


All Articles