Search code examples
pythonfunctionanimationkivywidget

How can I restart kivy animation every time its called upon?


I am making a simple weather app. The user enters a location and gets results in plain text. I have made a loading screen that has a spinning animation. When the program is loading these weather results, it changes to the loading screen and the animation spins well.

I have the animation set for a duration of 20 seconds for now. This allows the first instance of my load screen display more than enough time to yield results and then change to the results screen (normally about 5 seconds). That seemingly works well.

My issue is: Once the animation has completed its 20 second run (in the background) it doesn't re start when called for again. It seems the app will allow it to be called once only. For it to animate again, the app must be closed completely and restarted.

Ideally I'd like the animation to stop spinning and be reset as soon as the load screen has been exited (so once the results have been delivered). The user can get weather multiple times, so I need it to start again every time the load screen is displayed.

I have tried the undesired solution of having it constantly spinning in the background so that when the load screen is shown, its spinning.

But, even then I am having trouble getting it to repeat... (anim.repeat = True doesn't work for me)

Here is a working example that has been stripped back as much as possible to still behave the same as my real app.

main.py

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.animation import Animation
import time
from threading import Thread

class HomeScreen(Screen):
    pass
class Results1Screen(Screen):
    pass
class LoadScreen(Screen):
    pass

GUI = Builder.load_file("main.kv")
class TestApp(App):
    def build(self):
        return GUI

    def change_screen(self, screen_name):
        screen_manager = self.root.ids['screen_manager']
        screen_manager.current = screen_name

    def spin_widget(self):
        widget = self.root.ids["load_screen"].ids["spinning_widget"]
        anim = Animation(angle=-2000, duration=20)
        anim.repeat = True # this doesn't seem to work?
        anim.start(widget)

    def thread1(self): # this starts the actual "work" of the app by calling get_briefing() below
        p2 = Thread(target=lambda: self.get_briefing())
        p2.start()

    def get_briefing(self):
        time.sleep(5)# I have added a 5sec sleep to emulate work being done
        self.change_screen("results_1_screen") # screen change from loading screen to results screen after work complete
TestApp().run()

main.kv (just for screen managing)

#:import utils kivy.utils
#:include kv/homescreen.kv
#:include kv/results1screen.kv
#:include kv/loadscreen.kv
GridLayout:
    cols: 1
    FloatLayout:
        canvas:
            Rectangle:
                size: self.size
                pos: self.pos
        pos_hint: {"top": 1, "left": 1}
        size_hint: 1, 0
    ScreenManager:
        id: screen_manager
        HomeScreen:
            name: "home_screen"
            id: home_screen
        Results1Screen:
            name: "results_1_screen"
            id: results_1_screen
        LoadScreen:
            name: "load_screen"
            id: load_screen

homescreen.kv

<HomeScreen>:
    FloatLayout:
        canvas:
            Color:
                rgb: utils.get_color_from_hex("#000523")
            Rectangle:
                size: self.size
                pos: self.pos
    Button:
        text: "GO!"
        pos_hint:  {"top": .2, "center_x": .5}
        size_hint: .5, .08
        padding: 20, 20
        on_release:
            app.spin_widget()
            app.change_screen("load_screen") # widget spinning and going to load screen
            app.thread1() # start the app working in background

results1screen.kv (in real app, this has desired results displayed)

<Results1Screen>:
    FloatLayout:
        canvas:
            Color:
                rgb: utils.get_color_from_hex("#000523")
            Rectangle:
                size: self.size
                pos: self.pos
    Label:
        text: "RESULTS"
        pos_hint: {"top": .9, "center_x": .5}
        size_hint: 1, .1
    Button:
        text: "back"
        size_hint: .1, .1
        on_release:
            app.change_screen("home_screen")

loadscreen.kv

<LoadScreen>:
    FloatLayout:
        canvas:
            Color:
                rgb: utils.get_color_from_hex("#000523")
            Rectangle:
                size: self.size
                pos: self.pos
        Label:
            text: "loading"
        SpinningWidget:
            id: spinning_widget
            angle: 0
            size_hint: .1, .1
            pos_hint: {"center_x": .5, "center_y": .6}
<SpinningWidget@Image>
    angle: 0
    canvas.before:
        PushMatrix
        Rotate:
            angle: root.angle
            axis: 0, 0, 1
            origin: root.center
    canvas.after:
        PopMatrix

I have tried a couple of ways of exiting or resetting the spin widget function after the get_briefing function has finished but to no avail. I have tried anim.cancel() and things like exit(self.spin_widget()) in an attempt to reset it, ready for its next call up.

Can someone please help me out, I can't seem to find an answer in Kivy docs or anywhere else online for that matter. Thank you.


Solution

  • Try resetting the angle to 0 before each Animation:

    def spin_widget(self):
        widget = self.root.ids["load_screen"].ids["spinning_widget"]
        widget.angle = 0
        anim = Animation(angle=-2000, duration=20)
        anim.repeat = True # this doesn't seem to work?
        anim.start(widget)
    

    The problem is that after the first Animation, the angle value is -2000, so any Animation after that will not actually change the angle. Same problem with repeat.