Material Design. Animationen in Kivy erstellen



Grüße an alle Fans und Experten der Programmiersprache Python!



In diesem Artikel werde ich Ihnen zeigen, wie Sie mit Animationen im plattformübergreifenden Kivy- Framework in Verbindung mit der Google Material Design- Komponentenbibliothek - KivyMD - arbeiten . Wir werden uns die Struktur eines Kivy-Projekts ansehen und Materialkomponenten verwenden , um eine mobile Testanwendung mit einigen Animationen zu erstellen. Der Artikel wird nicht klein sein und viele GIF-Animationen enthalten. Gießen Sie also Kaffee ein und lassen Sie uns gehen!



Um das Interesse der Leser zu wecken, möchte ich sofort das Ergebnis dessen zeigen, was wir am Ende bekommen:





Für die Arbeit brauchen wir also das Kivy-Framework:



pip install kivy


Und die KivyMD-Bibliothek, die Material Design-Widgets für das Kivy-Framework bereitstellt:



pip install https://github.com/kivymd/KivyMD/archive/master.zip


Alles ist bereit zu gehen! Öffnen Sie PyCharm und erstellen Sie ein neues CallScreen- Projekt mit der folgenden Verzeichnisstruktur:





Die Struktur kann beliebig sein. Weder das Kivy-Framework noch die KivyMD-Bibliothek erfordern andere erforderliche Verzeichnisse als die Standardanforderung. Im Stammverzeichnis des Projekts muss sich eine Datei mit dem Namen main.py befinden . Dies ist der Einstiegspunkt in die Anwendung:





Im Daten- / Bildverzeichnis habe ich die für die Anwendung erforderlichen Grafikressourcen abgelegt:



Im Verzeichnis uix / screen / baseclass haben wir eine callscreen.py- Datei mit der gleichnamigen Python-Klasse, in der wir die Logik der Anwendungsbildschirmoperation implementieren:





Und im Verzeichnis uix / screen / kv erstellen wir eine callscreen.kv- Datei (lassen Sie sie vorerst leer) - mit einer Beschreibung der Benutzeroberfläche in der speziellen DSL Kivy-Sprache :





Wenn das Projekt erstellt wird, können wir die Datei callscreen.py öffnen und die Bildschirmklasse unserer Testanwendung implementieren.



callscreen.py:



import os

from kivy.lang import Builder

from kivymd.uix.screen import MDScreen

#    KV 
with open(os.path.join(os.getcwd(), "uix", "screens", "kv", "callscreen.kv"), encoding="utf-8") as KV:
    Builder.load_string(KV.read())


class CallScreen(MDScreen):
    pass


Die CallScreen- Klasse wird vom MDScreen- Widget der KivyMD- Bibliothek geerbt (fast allen Komponenten dieser Bibliothek wird MD - Material Design vorangestellt ). MDScreen ist ein Analogon zum Screen- Widget des Kivy-Frameworks aus dem Modul kivy.uix.screenmanager , jedoch mit zusätzlichen Eigenschaften. Mit MDScreen können Sie auch Widgets und Controller wie folgt übereinander platzieren:





Dies ist die Position, die wir verwenden, wenn wir schwebende Elemente auf dem Bildschirm platzieren.



Am Eintrittspunkt die Anwendung - die main.py Datei , erstellen Sie die TestCallScreen Klasse , von der geerbt MDApp Klasse mit einer obligatorischen Build - Methode , die einen Widget oder Layout zurückkehren muss es auf dem Bildschirm angezeigt werden soll . In unserem Fall ist dies die zuvor erstellte CallScreen- Bildschirmklasse .



main.py:



from kivymd.app import MDApp

from uix.screens.baseclass.callscreen import CallScreen


class TestCallScreen(MDApp):
    def build(self):
        return CallScreen()


TestCallScreen().run()


Dies ist eine vorgefertigte Anwendung, die einen leeren Bildschirm anzeigt. Wenn wir die Datei main.py ausführen , sehen wir:





Beginnen wir nun damit, den UI-Bildschirm in der Datei callscreen.kv zu markieren . Dazu müssen Sie mit der Basisklasse eine gleichnamige Regel erstellen, in der Widgets und ihre Eigenschaften beschrieben werden. Wenn wir beispielsweise eine Python-Klasse namens CallScreen haben , muss die Regel in der KV-Datei genau denselben Namen haben. Obwohl Sie alle Schnittstellenelemente direkt im Code erstellen können, ist dies, gelinde gesagt, nicht korrekt. Vergleichen Sie:



MyRootWidget:

    BoxLayout:

        Button:

        Button:


Und ein Python-Analogon:



root = MyRootWidget()
box = BoxLayout()
box.add_widget(Button())
box.add_widget(Button())
root.add_widget(box)


Es ist ziemlich offensichtlich, dass der Widget-Baum in Kv-Sprache viel besser lesbar ist als in Python-Code. Wenn die Widgets Argumente enthalten, wird Ihr Python-Code außerdem zu einem Chaos, und nach einem Tag können Sie es nicht mehr herausfinden. Wer also etwas sagt, wenn das Framework es Ihnen ermöglicht, UI-Elemente durch eine deklarative Sprache zu beschreiben, ist dies ein Plus. Nun, in Kivy ist dies ein doppeltes Plus, da Sie in Kv Language immer noch Python-Anweisungen ausführen können.



Beginnen wir also mit dem Titelbild:



callscreen.kv:



<CallScreen>

    FitImage:
        id: title_image  # id     
        size_hint_y: .45  #   (45%   )
        #  root     .
        #     <class 'uix.screens.baseclass.callscreen.CallScreen'>,
        #  self -    - <kivymd.utils.fitimage.FitImage object>.
        y: root.height - self.height  #    Y
        source: "data/images/avatar.jpg"  #   




Das FitImage- Widget wird automatisch gestreckt, um den gesamten ihm zugewiesenen Speicherplatz anzupassen , wobei das Seitenverhältnis des Bildes beibehalten wird:





Wir können die Datei main.py ausführen und das Ergebnis sehen:





Im Moment ist alles einfach und es ist Zeit, die Widgets zu animieren. Fügen Sie dem Bildschirm eine Schaltfläche hinzu, indem Sie drücken, welche Animationsmethoden aus der Python CallScreen- Klasse : callscreen.kv aufgerufen werden



:



#:import get_color_from_hex kivy.utils.get_color_from_hex
#:import colors kivymd.color_definitions.colors


<CallScreen>

    FitImage:
        [...]

    MDFloatingActionButton:
        icon: "phone"
        x: root.width - self.width - dp(20)
        y: app.root.height * 45 / 100 + self.height / 2
        md_bg_color: get_color_from_hex(colors["Green"]["A700"])
        on_release:
            #     .
            root.animation_title_image(title_image); \
            root.open_call_box = True if not root.open_call_box else False


Modulimporte in Kv-Sprache:



#:import get_color_from_hex kivy.utils.get_color_from_hex
#:import colors kivymd.color_definitions.colors


Wird den folgenden Importen in Python-Code ähnlich sein:



#  get_color_from_hex   
#      rgba.
from kivy.utils import get_color_from_hex
#      :
#
# colors = {
#     "Red": {
#         "50": "FFEBEE",
#         "100": "FFCDD2",
#         ...,
#     },
#     "Pink": {
#         "50": "FCE4EC",
#         "100": "F8BBD0",
#         ...,
#     },
#     ...
# }
#
# https://kivymd.readthedocs.io/en/latest/themes/color-definitions/
from kivymd.color_definitions import colors




Nach dem Starten und Klicken auf die grüne Schaltfläche erhalten wir - AttributeError: Das Objekt 'CallScreen' hat kein Attribut 'animation_title_image' . Kehren wir daher zur CallScreen- Datei callscreen.py der Basisklasse zurück und erstellen darin eine Methode animation_title_image , die das Titelbild animiert.



callscreen.py:



#     .
from kivy.animation import Animation

[...]

class CallScreen(MDScreen):
    #        .
    open_call_box = False

    def animation_title_image(self, title_image):
        """
        :type title_image: <kivymd.utils.fitimage.FitImage object>
        """

        if not self.open_call_box:
            #       .
            Animation(size_hint_y=1, d=0.6, t="in_out_quad").start(title_image)
        else:
            #       .
            Animation(size_hint_y=0.45, d=0.6, t="in_out_quad").start(title_image)


Wie Sie bereits verstanden haben, animiert die Animationsklasse wahrscheinlich wie in anderen Frameworks einfach eine Widget-Eigenschaft. In unserem Fall animieren wir die Eigenschaft size_hint_y - den Höhenhinweis - und legen das Ausführungsintervall für die Animation im Parameter d - Dauer und den Animationstyp im Parameter t - Typ fest. Wir können mehrere Eigenschaften eines Widgets gleichzeitig animieren und Animationen mit den Operatoren + , + = ... kombinieren. Das folgende Bild zeigt das Ergebnis unserer Arbeit. Zum Vergleich habe ich für das richtige GIF die Animationstypen in_elastic und out_elastic verwendet :



Unser nächster Schritt besteht darin, dem Titelbild einen Unschärfeeffekt hinzuzufügen. Für diese Zwecke verfügt Kivy über ein EffectWidget . Wir müssen die gewünschten Eigenschaften für den Effekt festlegen und das Titelbild-Widget im EffectWidget platzieren.



callscreen.kv:



#:import effect kivy.uix.effectwidget.EffectWidget
#:import HorizontalBlurEffect kivy.uix.effectwidget.HorizontalBlurEffect
#:import VerticalBlurEffect kivy.uix.effectwidget.VerticalBlurEffect


<CallScreen>

    EffectWidget:
        effects:
            # blur_value   .
            (\
            HorizontalBlurEffect(size=root.blur_value), \
            VerticalBlurEffect(size=root.blur_value), \
            )

        FitImage:
            [...]

    MDFloatingActionButton:
        [...]
        on_release:
            #    blur .
            root.animation_blur_value(); \
            [...]


Jetzt müssen wir das Attribut blur_value zur Python CallScreen- Basisklasse hinzufügen und eine Methode animation_blur_value erstellen , die den Wert des Unschärfeeffekts animiert .



callscreen.py:



from kivy.properties import NumericProperty
[...]


class CallScreen(MDScreen):
    #     EffectWidget.
    blur_value = NumericProperty(0)

    [...]

    def animation_blur_value(self):
        if not self.open_call_box:
            Animation(blur_value=15, d=0.6, t="in_out_quad").start(self)
        else:
            Animation(blur_value=0, d=0.6, t="in_out_quad").start(self)


Ergebnis:





Beachten Sie, dass Animationsmethoden asynchron ausgeführt werden! Animieren wir die grüne Ruftaste, damit sie unsere Augen nicht stört.



callscreen.py:



from kivy.utils import get_color_from_hex
from kivy.core.window import Window

from kivymd.color_definitions import colors

[...]


class CallScreen(MDScreen):
    [...]

    def animation_call_button(self, call_button):
        if not self.open_call_box:
            Animation(
                x=self.center_x - call_button.width / 2,
                y=dp(40),
                md_bg_color=get_color_from_hex(colors["Red"]["A700"]),
                d=0.6,
                t="in_out_quad",
            ).start(call_button)
        else:
            Animation(
                y=Window.height * 45 / 100 + call_button.height / 2,
                x=self.width - call_button.width - dp(20),
                md_bg_color=get_color_from_hex(colors["Green"]["A700"]),
                d=0.6,
                t="in_out_quad",
            ).start(call_button)


callscreen.kv:



[...]

<CallScreen>

    EffectWidget:
        [...]

        FitImage:
            [...]

    MDFloatingActionButton:
        [...]
        on_release:
            #     .
            root.animation_call_button(self); \
            [...]




Fügen wir dem Hauptbildschirm zwei Elemente vom Typ TwoLineAvatarListItem hinzu .



callscreen.kv:



#:import STANDARD_INCREMENT kivymd.material_resources.STANDARD_INCREMENT
#:import IconLeftWidget kivymd.uix.list.IconLeftWidget

[...]


<ItemList@TwoLineAvatarListItem>
    icon: ""
    font_style: "Caption"
    secondary_font_style: "Caption"
    height: STANDARD_INCREMENT

    IconLeftWidget:
        icon: root.icon


<CallScreen>

    EffectWidget:
        [...]

        FitImage:
            [...]

    MDBoxLayout:
        id: list_box
        orientation: "vertical"
        adaptive_height: True
        y: root.height * 45 / 100 - self.height / 2

        ItemList:
            icon: "phone"
            text: "Phone"
            secondary_text: "123 456 789"

        ItemList:
            icon: "mail"
            text: "Email"
            secondary_text: "kivydevelopment@gmail.com"

    MDFloatingActionButton:
        [...]
        on_release:
            root.animation_list_box(list_box); \
            [...]




Wir haben zwei ItemList-Elemente erstellt und in einem vertikalen Feld platziert. Wir können eine neue Methode animation_list_box in der CallScreen- Klasse erstellen , um diese Box zu animieren.



callscreen.py:



[...]


class CallScreen(MDScreen):
    [...]

    def animation_list_box(self, list_box):
        if not self.open_call_box:
            Animation(
                y=-list_box.y,
                opacity=0,
                d=0.6,
                t="in_out_quad"
            ).start(list_box)
        else:
            Animation(
                y=self.height * 45 / 100 - list_box.height / 2,
                opacity=1,
                d=0.6,
                t="in_out_quad",
            ).start(list_box)




Fügen wir dem Bildschirm eine Symbolleiste hinzu.



callscreen.kv:



[...]

<CallScreen>

    EffectWidget:
        [...]

        FitImage:
            [...]

    MDToolbar:
        y: root.height - self.height - dp(20)
        md_bg_color: 0, 0, 0, 0
        opposite_colors: True
        title: "Profile"
        left_action_items:  [["menu", lambda x: x]]
        right_action_items: [["dots-vertical", lambda x: x]]

    MDBoxLayout:
        [...]

        ItemList:
            [...]

        ItemList:
            [...]

    MDFloatingActionButton:
        [...]




Avatar und Benutzername.



callscreen.kv:



[...]

<CallScreen>

    EffectWidget:
        [...]

        FitImage:
            [...]

    MDToolbar:
        [...]

    MDFloatLayout:
        id: round_avatar
        size_hint: None, None
        size: "105dp", "105dp"
        md_bg_color: 1, 1, 1, 1
        radius: [self.height / 2,]
        y: root.height * 45 / 100 + self.height
        x: root.center_x - (self.width + user_name.width + dp(20)) / 2

        FitImage:
            size_hint: None, None
            size: "100dp", "100dp"
            mipmap: True
            source: "data/images/round-avatar.jpg"
            radius: [self.height / 2,]
            pos_hint: {"center_x": .5, "center_y": .5}
            mipmap: True

    MDLabel:
        id: user_name
        text: "Irene"
        font_style: "H3"
        bold: True
        size_hint: None, None
        -text_size: None, None
        size: self.texture_size
        theme_text_color: "Custom"
        text_color: 1, 1, 1, 1
        y: round_avatar.y + self.height / 2
        x: round_avatar.x + round_avatar.width + dp(20)

    MDBoxLayout:
        [...]

        ItemList:
            [...]

        ItemList:
            [...]

    MDFloatingActionButton:
        root.animation_round_avatar(round_avatar, user_name); \
        root.animation_user_name(round_avatar, user_name); \
        [...]




Typische Animation der X- und Y- Positionen eines Avatars und Benutzernamens.



callscreen.py:



[...]


class CallScreen(MDScreen):
    [...]

    def animation_round_avatar(self, round_avatar, user_name):
        if not self.open_call_box:
            Animation(
                x=self.center_x - round_avatar.width / 2,
                y=round_avatar.y + dp(50),
                d=0.6,
                t="in_out_quad",
            ).start(round_avatar)
        else:
            Animation(
                x=self.center_x - (round_avatar.width + user_name.width + dp(20)) / 2,
                y=self.height * 45 / 100 + round_avatar.height,
                d=0.6,
                t="in_out_quad",
            ).start(round_avatar)

    def animation_user_name(self, round_avatar, user_name):
        if not self.open_call_box:
            Animation(
                x=self.center_x - user_name.width / 2,
                y=user_name.y - STANDARD_INCREMENT,
                d=0.6,
                t="in_out_quad",
            ).start(self.ids.user_name)
        else:
            Animation(
                x=round_avatar.x + STANDARD_INCREMENT,
                y=round_avatar.center_y - user_name.height - dp(20),
                d=0.6,
                t="in_out_quad",
            ).start(user_name)




Wir müssen nur eine Box mit Schaltflächen erstellen:





Zum Zeitpunkt dieses Schreibens bin ich auf die Tatsache gestoßen, dass die erforderliche Schaltfläche nicht in der KivyMD-Bibliothek gefunden wurde . Ich musste es schnell selbst schaffen. Ich habe der vorhandenen MDIconButton- Klasse einfach Canvas- Anweisungen hinzugefügt , einen Kreis um die Schaltfläche definiert und ihn zusammen mit der Beschriftung in einem vertikalen Feld platziert.



callscreen.kv:



<CallBoxButton@MDBoxLayout>
    orientation: "vertical"
    adaptive_size: True
    spacing: "8dp"
    icon: ""
    text: ""

    MDIconButton:
        icon: root.icon
        theme_text_color: "Custom"
        text_color: 1, 1, 1, 1

        canvas:
            Color:
                rgba: 1, 1, 1, 1
            Line:
                width: 1
                circle:
                    (\
                    self.center_x, \
                    self.center_y, \
                    min(self.width, self.height) / 2, \
                    0, \
                    360, \
                    )

    MDLabel:
        text: root.text
        size_hint_y: None
        height: self.texture_size[1]
        font_style: "Caption"
        halign: "center"
        theme_text_color: "Custom"
        text_color: 1, 1, 1, 1

[...]




Als Nächstes erstellen wir eine Box für die benutzerdefinierten Schaltflächen.



callscreen.kv:



<CallBox@MDGridLayout>
    cols: 3
    rows: 2
    adaptive_size: True
    spacing: "24dp"

    CallBoxButton:
        icon: "microphone-off"
        text: "Mute"
    CallBoxButton:
        icon: "volume-high"
        text: "Speaker"
    CallBoxButton:
        icon: "dialpad"
        text: "Keypad"

    CallBoxButton:
        icon: "plus-circle"
        text: "Add call"
    CallBoxButton:
        icon: "call-missed"
        text: "Transfer"
    CallBoxButton:
        icon: "account"
        text: "Contact"

[...]




Jetzt platzieren wir die erstellte CallBox in der CallScreen- Regel und legen ihre Position entlang der Y- Achse jenseits des unteren Bildschirmrandes fest.



callscreen.kv:



[...]

<CallScreen>

    EffectWidget:
        [...]

        FitImage:
            [...]

    MDToolbar:
        [...]

    MDFloatLayout:
        [...]

        FitImage:
            [...]

    MDLabel:
        [...]

    MDBoxLayout:
        [...]

        ItemList:
            [...]

        ItemList:
            [...]

    MDFloatingActionButton:
        root.animation_call_box(call_box, user_name); \
        [...]

    CallBox:
        id: call_box
        pos_hint: {"center_x": .5}
        y: -self.height
        opacity: 0


Es bleibt nur die Position der erstellten Box mit Schaltflächen zu animieren.



callscreen.py:



from kivy.metrics import dp
[...]


class CallScreen(MDScreen):
    [...]

    def animation_call_box(self, call_box, user_name):
        if not self.open_call_box:
            Animation(
                y=user_name.y - call_box.height - dp(100),
                opacity=1,
                d=0.6,
                t="in_out_quad",
            ).start(call_box)
        else:
            Animation(
                y=-call_box.height,
                opacity=0,
                d=0.6,
                t="in_out_quad",
            ).start(call_box)






Endgültiges GIF mit einem Test auf einem mobilen Gerät:





Das ist alles, ich hoffe es war nützlich!



All Articles