Search code examples
pythonkivykivy-languagekivymd

How to change the screen from one to another after gif plays only once


For the last few days I have been struggling with seemingly trivial problem of changing screens in Kivy. I have this gif animation that lasts 6 seconds (at least according to Canva as I created it in Canva). I have a screen with this gif that I want to play before the players get the chance to play the game. Just like an introduction.

So what I want is I want this gif to be played only once and then I want to immediately transition to the main game.

Final Round Animation gif

** PROBLEMS THAT I ENCOUNTERED:**

  1. The gif is not played from the start. It starts kinda from the end despite the fact that if you download it and open it, it plays normally. In Kivy sometimes it starts in the middle, sometimes it starts from the end etc. It's very irregular and I have no idea what has an impact on it.

  2. I don't know how to tell Kivy to change the screen after the animation is completed. I tried using Clock.schedule_once but for some reason I can't get it to work despite the fact I had been following advices that I found here on Stack Overflow and also on YouTube.

I'm not going to include the whole code as it's very long (as I mentioned I've been working on this project for a while and it consists of several different classes and files) but below you can find my python and Kivy file for the "final_round_animation". **All I need is to move it to some different screen. You can use a blank screen with a white background. It doesn't matter. All I need is to find out how to play this gif once correctly, and then move from this screen to another one. **

py file


from kivy.uix.screenmanager import Screen


class FinalRoundAnimation(Screen):
    pass

kivy file:


<FinalRoundAnimation>

    name: 'final_round_animation'

    FloatLayout:
        orientation: 'horizontal'
        size: 1280, 720

        Image:
            id: gif
            source:'Final Round Animation.gif'
            anim_delay: 0.03
            allow_stretch: True
            keep_ratio: False
            keep_data: True

I've spent literally days already trying to find the solution here on Stack Overflow and also other places. But what I tried was:

  1. I tried using ScreenManager and put those two screens ("final_round_animation", "main_game_trial") together in one class and one kivy file. Next, I tried using Clock.schedule_once to schedule transition after 6 seconds (duration of the gif) just like in this video here:

https://youtu.be/fq9yA_XVzLA?t=329

It was something in this style:

from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.clock import Clock


class Swap(ScreenManager):
    
    def on_enter(self):
        Clock.schedule_once(self.change_screen, 6)

    def change_screen(self):
        self.current = 'main_game_trial'


class FinalRoundAnimation(Screen):
    pass


class MainGameTrial(Screen):
    pass

The problems with this approach were as below"

  • Clock.schedule_once didn't even work at all. The gif kept on looping endlessly as if this on_enter function was not visible at all. I also tried to write it like this with parenthesis after the name of the function but it didn't change anything:

Clock.schedule_once(self.change_screen(), 6)

I came to conclusion that maybe the fact that it didn't work like in the video was because I tried to do it within some class that is some part of the project and in that video it was shown that it was made in the file with the App.

  • I remember that I definitely encountered "kivy.uix.screenmanager.ScreenManagerException: No Screen with name" when I tried to use "main_game_trial" for the first time. Anyway, even when I fixed this error the transition didn't happen at all even if the screen name was recognised.
  1. I tried using anim_loop within kivy file and then referencing it in the py file. Basically I wanted to create a condition that if anim_loop will be greater than 1 then move to a different screen. However, to no avail. When I set anim_loop for example to 2, the gif wasn't playing at all. I don't know why. If I set it to 10 it kept looping endlessly.

  2. Tried to apply tens of different situations from Stack Overflow threads that I went over when I was searching for solution.

I would be very very grateful if you could help me with this.

Thank you in advance for your input.


Solution

  • Ideally, the Image class would fire an event when the gif animation ends, but it does not. You can simulate this by extending the Image class:

    class MyImage(Image):
        complete = BooleanProperty(False)
    
        def __init__(self, **kwargs):
            super().__init__(**kwargs)
            self.anim_loop = 1  # only allow one animation loop
            self.textures_used = 0
            self.num_textures = None
    
        def on_texture(self, image, texture):
            if self.num_textures is None:
                self.num_textures = len(self._coreimage.image.textures)
            self.textures_used += 1
            if (self.textures_used + 1) == self.num_textures:
                self.complete = True
    

    Then use that new class in your kv:

    <FinalRoundAnimation>:
        name: 'final_round_animation'
    
        FloatLayout:
            orientation: 'horizontal'
            size: 1280, 720
    
            MyImage:
                id: gif
                source: 'Final Round Animation.gif'
                anim_delay: -1   # do not run the animation
                allow_stretch: True
                keep_ratio: False
                keep_data: True
                on_complete:
                    if self.complete: root.manager.current='main_game_trial'; \
                        self.anim_delay = -1; \
                        self.texture = self._coreimage.image.textures[0]
                    else: root.manager.current='final_round_animation'
    

    the on_complete now triggers the Screen change.

    And add an on_enter() method to the `FinalRoundAnimation' class:

    class FinalRoundAnimation(Screen):
        def on_enter(self, *args):
            gif = self.ids.gif
            gif.reload()
            gif.textures_used = 0
            gif.num_textures = None
            gif.complete = False
            gif.anim_delay = 0.03  # starts the animation