Search code examples
python-3.xkivykivymd

How to make MDCard expanding and collapsing with animation in Kivy


I am searching for a way to animate my MDCard when expanding or collapsing.

In my example, when I click my MDIconButton I want an animation that slowly expands or collapses the card. I know there is an MDExpansionsPanel widget but for now, I'm not interested in using this. Is there an easy way to implement an animation that is similar to the ExpansionPanel?

main.kv

MDScreen:
    MDBoxLayout:
        orientation: "vertical"

        MDCard:
            id: card
            orientation: "vertical"
            md_bg_color: .7, .7, .7, 1
            padding: dp(20)
            size_hint: .5, None
            height: self.minimum_height
            pos_hint: {"center_x": .5}

            MDIconButton:
                icon: "chevron-down"
                on_press: app.on_chevron()

        Widget:

<Box>:
    orientation: "vertical"
    size_hint_y: None
    height: self.minimum_height

main.py

from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.core.window import Window
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.label import MDLabel

Window.size = {320, 600}

class Box(MDBoxLayout):
    pass

class MainApp(MDApp):
    def build(self):
        self.is_expanded = False
        return Builder.load_file("main.kv")

    def on_chevron(self):
        self.is_expanded = not self.is_expanded
        card_layout = self.root.ids.card

        if self.is_expanded:
            item = Box()
            for i in range(10):
                item.add_widget(MDLabel(text=f"Lbl: {i}", size_hint=(1, None), height=20))
            card_layout.add_widget(item)

        else:
            card_layout.remove_widget(card_layout.children[0])

if __name__ == "__main__":
    MainApp().run()

Solution

  • You can use Animation to do that. Here is a modified version of your on_chevron() method that uses Animation:

    def on_chevron(self):
        self.is_expanded = not self.is_expanded
        card_layout = self.root.ids.card
    
        if self.is_expanded:
            item = Box()
            labels = []
            for i in range(10):
                # initialze Label with 0 height and font_size
                l = Label(text=f"Lbl: {i}", color=(0,0,0,1), font_size=0, size_hint=(1, None), height=0)
                item.add_widget(l)
                labels.append(l)
            card_layout.add_widget(item)
            self.animate_labels(labels)
    
        else:
            card_layout.remove_widget(card_layout.children[0])
    
    def animate_labels(self, labels):
        anims = []
        for i in range(len(labels)):
            anims.append(Animation(height=20, font_size=15)) # animate height and font_size
        for i in range(len(labels)):
            anims[i].start(labels[i])  # start animations
    

    I switched your MDLabel to Label just because MDLabel meddles with sizes. There may be a way to do this with the MDLabel.