Material Python. Tarjetas personalizadas con efectos OpenGL


¡Saludos, queridos amantes y expertos en Python!

En este artículo, le mostraré cómo aplicar efectos OpenGL a sus tarjetas personalizadas si utiliza herramientas multiplataforma como el marco Kivy y la biblioteca de diseño de materiales para este marco, KivyMD , en sus aplicaciones . ¡Vamonos!

KivyMD tiene un componente estándar MDCard : la clase base para crear varias tarjetas personalizadas ( especificaciones de diseño de materiales, tarjetas ). Si no entra en detalles, debajo del capó de la MDCard hay un BoxLayout normal , un contenedor que le permite colocar otros widgets en una orientación vertical u horizontal. Es decir, si necesita hacer algún tipo de tarjeta, por ejemplo, información sobre el usuario, hágalo usted mismo. MDCard solo implementa ripple_behavior , touch_behavior y proyecta sombras:

Un ejemplo de un programa que muestra una tarjeta en blanco es el siguiente:

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:


Se ve bastante simple. Pero, ¿qué pasa si queremos una hermosa tarjeta con un efecto de Desenfoque en caso de recibir foco? Como, por ejemplo, en la aplicación Flutter UI Designs :


¡Tendrás que hacerlo tú mismo! Además, no hay nada complicado en esto. Primero, cree la clase base del mapa futuro:

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

La imagen principal de la tarjeta:


Imagen de la sombra:


Ahora llenaremos el mapa con componentes cuyas propiedades hemos determinado utilizando el lenguaje especial DSL Kv-Language , diseñado para un diseño conveniente de diseños de interfaz:

<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

Ponemos el widget RelativeLayout en el mapa , lo que nos permite mezclar los componentes uno encima del otro de esta manera:


Primero, colocamos la imagen principal, colocamos una sombra y texto encima. Ahora si ejecutamos nuestro 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()

... obtenemos el resultado:


Y el resultado, por supuesto, está lejos del esperado, porque no veremos ningún efecto de desenfoque o bordes redondeados en la tarjeta. Comencemos con el efecto de desenfoque. Kivy tiene un widget de EffectWidget estándar que puede aplicar varios efectos gráficos a sus hijos. Funciona renderizando instancias de Fbo utilizando sombreadores personalizados OpenGL. Necesitamos aplicar el efecto de desenfoque a la imagen principal y la imagen de sombra en la tarjeta. Por lo tanto, debemos poner sus componentes en el 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"

    ...    

Agregue un campo para el valor del grado de efecto de desenfoque:

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

Comenzamos y vemos:


Cuando pasa el mouse (si es un escritorio) o toca (si es móvil) no sucede nada. Para que la tarjeta responda al evento on_focus, debemos habilitar la lectura de este evento en las propiedades de la regla de RestaurantCard y asignar métodos que se ejecutarán cuando se registre este evento:

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

Ya mejor:


Para recortar las esquinas de la tarjeta, decidí aplicar una plantilla (plantilla) al widget de EffectWidget:

#:import Stencil kivymd.uix.graphics.Stencil


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


<RestaurantCard>
    ...

    RelativeLayout:

        Effect:
            ...

Y ahora todo funciona como lo planeamos:


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


Bueno, finalmente, quiero mostrar un video en el que funcionan dos programas: uno, escrito usando el marco Flutter, y el segundo, usando Kivy y KivyMD. Al final del artículo, les dejo una encuesta en la que deben adivinar qué tecnología se utiliza y dónde.


All Articles