Python 为什么动画在恢复后会加速?

Python 为什么动画在恢复后会加速?,python,python-3.x,animation,tkinter,Python,Python 3.x,Animation,Tkinter,我正在制作tkinter的单摆动画。我已将s和r绑定到窗口以停止并恢复动画。但我注意到了一个我无法解释的奇怪现象:如果我在钟摆没有停止时按r,它就会加速。我不明白为什么会这样。有人能解释发生了什么以及如何解决吗 这是我的密码: from math import sqrt, cos, sin, radians from tkinter import * class SimplePendulum: def __init__(self): # Create a window

我正在制作tkinter的单摆动画。我已将s和r绑定到窗口以停止并恢复动画。但我注意到了一个我无法解释的奇怪现象:如果我在钟摆没有停止时按r,它就会加速。我不明白为什么会这样。有人能解释发生了什么以及如何解决吗

这是我的密码:

from math import sqrt, cos, sin, radians
from tkinter import *

class SimplePendulum:
    def __init__(self):
        # Create a window
        win = Tk()
        win.title('Pendulum')

        # Create a canvas
        self.w, self.h = 250, 300
        self.canvas = Canvas(win, width=self.w, height=self.h, bg='white')
        self.canvas.pack()

        # Bind keys to the window
        win.bind('s', self.stop)
        win.bind('S', self.stop)
        win.bind('r', self.resume)
        win.bind('R', self.resume)

        # Pendulum constants
        self.g = 1
        self.L = 4*self.h/5
        self.theta_i = radians(20)

        # Initialize time t to 0
        self.t = 0

        # Start animation
        self.isStopped = False
        self.speed = 1/50           # initial frequency of oscillation
        self.animate()

        # Start the event loop
        win.mainloop()

    def drawPendulum(self):
        # Angle of the pendulum (from the vertial) at time t
        theta = self.theta_i * cos(sqrt(self.g/self.L) * self.t)
        # The two ends of the cord
        x_i, y_i = self.w/2, self.h/10
        x_f, y_f = x_i + self.L*sin(theta), y_i + self.L*cos(theta)
        # Draw the cord and bob of the pendulum
        self.canvas.create_line(x_i, y_i, x_f, y_f, tags='cord')
        rad = min(self.w, self.h)/20
        self.canvas.create_oval(x_f - rad, y_f - rad,
                                x_f + rad, y_f + rad, fill='red', tags='bob')

    def animate(self):
        if not self.isStopped:
            self.canvas.delete(ALL)
            self.drawPendulum()
            self.t += 2
            self.canvas.after(int(1/self.speed), self.animate)

    def stop(self, event):
        self.isStopped = True

    def resume(self, event):
        self.isStopped = False
        self.animate()

SimplePendulum()
只需将resume替换为:

如果在不检查self.isStopped标志的情况下运行resume,则它会运行一个新的动画,该动画有自己的调用递归,因此每次调用resume时,它都会调用线性递增的动画方法,而不检查标志。

只需将resume替换为:


当运行resume时不带if self.isStopped标志检查,它会运行一个新的动画,该动画有自己的调用递归,因此每次调用resume时,它都会调用线性递增的动画方法,而不带标志。

或者,您可以使用after\u cancel取消动画队列。但不确定这是否有好处:

from math import sqrt, cos, sin, radians
from tkinter import *

class SimplePendulum:
    def __init__(self):
        # Create a window
        win = Tk()
        win.title('Pendulum')

        # Create a canvas
        self.w, self.h = 250, 300
        self.canvas = Canvas(win, width=self.w, height=self.h, bg='white')
        self.canvas.pack()

        # Bind keys to the window
        win.bind('s', self.stop)
        win.bind('S', self.stop)
        win.bind('r', self.resume)
        win.bind('R', self.resume)

        # Pendulum constants
        self.g = 1
        self.L = 4*self.h/5
        self.theta_i = radians(20)

        # Initialize time t to 0
        self.t = 0
        self._queue = False

        # Start animation
        self.isStopped = False
        self.speed = 1/50           # initial frequency of oscillation
        self.animate()

        # Start the event loop
        win.mainloop()

    def drawPendulum(self):
        # Angle of the pendulum (from the vertial) at time t
        theta = self.theta_i * cos(sqrt(self.g/self.L) * self.t)
        # The two ends of the cord
        x_i, y_i = self.w/2, self.h/10
        x_f, y_f = x_i + self.L*sin(theta), y_i + self.L*cos(theta)
        # Draw the cord and bob of the pendulum
        self.canvas.create_line(x_i, y_i, x_f, y_f, tags='cord')
        rad = min(self.w, self.h)/20
        self.canvas.create_oval(x_f - rad, y_f - rad,
                                x_f + rad, y_f + rad, fill='red', tags='bob')

    def animate(self):
        self.canvas.delete(ALL)
        self.drawPendulum()
        self.t += 2
        self._queue = self.canvas.after(int(1/self.speed), self.animate)

    def stop(self, event):
        if self._queue:
            self.canvas.after_cancel(self._queue)
            self._queue = False

    def resume(self, event):
        if not self._queue:
            self.animate()

SimplePendulum()

或者,可以使用“取消后”来取消动画队列。但不确定这是否有好处:

from math import sqrt, cos, sin, radians
from tkinter import *

class SimplePendulum:
    def __init__(self):
        # Create a window
        win = Tk()
        win.title('Pendulum')

        # Create a canvas
        self.w, self.h = 250, 300
        self.canvas = Canvas(win, width=self.w, height=self.h, bg='white')
        self.canvas.pack()

        # Bind keys to the window
        win.bind('s', self.stop)
        win.bind('S', self.stop)
        win.bind('r', self.resume)
        win.bind('R', self.resume)

        # Pendulum constants
        self.g = 1
        self.L = 4*self.h/5
        self.theta_i = radians(20)

        # Initialize time t to 0
        self.t = 0
        self._queue = False

        # Start animation
        self.isStopped = False
        self.speed = 1/50           # initial frequency of oscillation
        self.animate()

        # Start the event loop
        win.mainloop()

    def drawPendulum(self):
        # Angle of the pendulum (from the vertial) at time t
        theta = self.theta_i * cos(sqrt(self.g/self.L) * self.t)
        # The two ends of the cord
        x_i, y_i = self.w/2, self.h/10
        x_f, y_f = x_i + self.L*sin(theta), y_i + self.L*cos(theta)
        # Draw the cord and bob of the pendulum
        self.canvas.create_line(x_i, y_i, x_f, y_f, tags='cord')
        rad = min(self.w, self.h)/20
        self.canvas.create_oval(x_f - rad, y_f - rad,
                                x_f + rad, y_f + rad, fill='red', tags='bob')

    def animate(self):
        self.canvas.delete(ALL)
        self.drawPendulum()
        self.t += 2
        self._queue = self.canvas.after(int(1/self.speed), self.animate)

    def stop(self, event):
        if self._queue:
            self.canvas.after_cancel(self._queue)
            self._queue = False

    def resume(self, event):
        if not self._queue:
            self.animate()

SimplePendulum()

与您的问题不太相关,但如果您更新画布上的元素,而不是清除整个画布并从头开始重新绘制,则动画会更平滑:

from math import sqrt, cos, sin, radians
from tkinter import *

class SimplePendulum:
    def __init__(self):
        # Create a window
        win = Tk()
        win.title('Pendulum')

        # Create a canvas
        self.w, self.h = 250, 300
        self.canvas = Canvas(win, width=self.w, height=self.h, bg='white')
        self.canvas.pack()

        # Bind keys to the window
        win.bind('s', self.stop)
        win.bind('S', self.stop)
        win.bind('r', self.resume)
        win.bind('R', self.resume)

        # Pendulum constants
        self.g = 1
        self.L = 4*self.h/5
        self.theta_i = radians(20)

        # Initialize time t to 0
        self.t = 0
        cord, bob = self.calcPendulum()
        self.cord = self.canvas.create_line(*cord, tags='cord')
        self.bob = self.canvas.create_oval(*bob, fill='red', tags='bob')

        # Start animation
        self.isStopped = False
        self.speed = 1/50           # initial frequency of oscillation
        self.animate()

        # Start the event loop
        win.mainloop()

    def calcPendulum(self):
        # Angle of the pendulum (from the vertial) at time t
        theta = self.theta_i * cos(sqrt(self.g/self.L) * self.t)
        # The two ends of the cord
        x_i, y_i = self.w/2, self.h/10
        x_f, y_f = x_i + self.L*sin(theta), y_i + self.L*cos(theta)
        rad = min(self.w, self.h)/20
        cord_pos = x_i, y_i, x_f, y_f
        bob_pos = x_f - rad, y_f - rad, x_f + rad, y_f + rad
        return cord_pos, bob_pos

    def animate(self):
        if not self.isStopped:
            cord, bob = self.calcPendulum()
            self.canvas.coords(self.cord, *cord)
            self.canvas.coords(self.bob, *bob)
            self.t += 2
            self.canvas.after(int(1/self.speed), self.animate)

    def stop(self, event):
        self.isStopped = True

    def resume(self, event):
        self.isStopped = False
        self.animate()

SimplePendulum()
而且,在这里上课也没有意义。我怀疑你这样做是因为你看到或被告知GUI中的所有东西都需要一个类。但关键是它需要是GUI小部件的子类。例如,您可以使SimplePendulum类成为画布的一种类型:

from math import sqrt, cos, sin, radians
from tkinter import *

class SimplePendulum(Canvas):
    def __init__(self, master=None, **kwargs):
        Canvas.__init__(self, master, bg='white', **kwargs)

        # Bind keys to the window
        master.bind('s', self.stop)
        master.bind('S', self.stop)
        master.bind('r', self.resume)
        master.bind('R', self.resume)

        # Pendulum constants
        self.g = 1
        self.theta_i = radians(20)

        # Initialize time t to 0
        self.t = 0
        cord, bob = self.calcPendulum()
        self.cord = self.create_line(*cord, tags='cord')
        self.bob = self.create_oval(*bob, fill='red', tags='bob')

        # Start animation
        self.timer = ''
        self.speed = 1/50           # initial frequency of oscillation
        self.animate()

    def calcPendulum(self):
        # Angle of the pendulum (from the vertial) at time t
        L = 4*self.winfo_height()/5
        theta = self.theta_i * cos(sqrt(self.g/L) * self.t)
        # The two ends of the cord
        x_i, y_i = self.winfo_width()/2, self.winfo_height()/10
        x_f, y_f = x_i + L*sin(theta), y_i + L*cos(theta)
        rad = min(self.winfo_width(), self.winfo_height())/20
        cord_pos = x_i, y_i, x_f, y_f
        bob_pos = x_f - rad, y_f - rad, x_f + rad, y_f + rad
        return cord_pos, bob_pos

    def animate(self):
        cord, bob = self.calcPendulum()
        self.coords(self.cord, *cord)
        self.coords(self.bob, *bob)
        self.t += 2
        self.timer = self.after(int(1/self.speed), self.animate)

    def stop(self, event=None):
        self.after_cancel(self.timer)

    def resume(self, event=None):
        self.stop() # in case it's currently running, stop it
        self.animate()

def main():
    # Create a window
    win = Tk()
    win.title('Pendulum')
    part = SimplePendulum(win, width=200, height=300)
    part.pack(fill=BOTH, expand=True)
    win.mainloop() # Start the event loop

if __name__ == '__main__':
    main()
现在你可以像我一样在一个小演示程序中使用你的新部件,或者将它打包到任何更大的程序中。或者多次使用它

我还移动了其他一些东西,比如将长度计算放在计时步骤中,这样您就可以调整窗口的大小,钟摆也可以调整大小。你的数学很有趣,因为它很好地演示了钟摆长度和频率之间的关系。嗯,现在我们有了一个整洁的小部件,我们可以通过在屏幕上放一个短的和一个高的来轻松演示:

def main():
    # Create a window
    win = Tk()
    win.title('Pendulum')
    part = SimplePendulum(win, width=200, height=100)
    part.pack(side=LEFT)
    part = SimplePendulum(win, width=400, height=600)
    part.pack(side=LEFT)
    win.mainloop() # Start the event loop

与您的问题不太相关,但如果您更新画布上的元素,而不是清除整个画布并从头开始重新绘制,则动画会更平滑:

from math import sqrt, cos, sin, radians
from tkinter import *

class SimplePendulum:
    def __init__(self):
        # Create a window
        win = Tk()
        win.title('Pendulum')

        # Create a canvas
        self.w, self.h = 250, 300
        self.canvas = Canvas(win, width=self.w, height=self.h, bg='white')
        self.canvas.pack()

        # Bind keys to the window
        win.bind('s', self.stop)
        win.bind('S', self.stop)
        win.bind('r', self.resume)
        win.bind('R', self.resume)

        # Pendulum constants
        self.g = 1
        self.L = 4*self.h/5
        self.theta_i = radians(20)

        # Initialize time t to 0
        self.t = 0
        cord, bob = self.calcPendulum()
        self.cord = self.canvas.create_line(*cord, tags='cord')
        self.bob = self.canvas.create_oval(*bob, fill='red', tags='bob')

        # Start animation
        self.isStopped = False
        self.speed = 1/50           # initial frequency of oscillation
        self.animate()

        # Start the event loop
        win.mainloop()

    def calcPendulum(self):
        # Angle of the pendulum (from the vertial) at time t
        theta = self.theta_i * cos(sqrt(self.g/self.L) * self.t)
        # The two ends of the cord
        x_i, y_i = self.w/2, self.h/10
        x_f, y_f = x_i + self.L*sin(theta), y_i + self.L*cos(theta)
        rad = min(self.w, self.h)/20
        cord_pos = x_i, y_i, x_f, y_f
        bob_pos = x_f - rad, y_f - rad, x_f + rad, y_f + rad
        return cord_pos, bob_pos

    def animate(self):
        if not self.isStopped:
            cord, bob = self.calcPendulum()
            self.canvas.coords(self.cord, *cord)
            self.canvas.coords(self.bob, *bob)
            self.t += 2
            self.canvas.after(int(1/self.speed), self.animate)

    def stop(self, event):
        self.isStopped = True

    def resume(self, event):
        self.isStopped = False
        self.animate()

SimplePendulum()
而且,在这里上课也没有意义。我怀疑你这样做是因为你看到或被告知GUI中的所有东西都需要一个类。但关键是它需要是GUI小部件的子类。例如,您可以使SimplePendulum类成为画布的一种类型:

from math import sqrt, cos, sin, radians
from tkinter import *

class SimplePendulum(Canvas):
    def __init__(self, master=None, **kwargs):
        Canvas.__init__(self, master, bg='white', **kwargs)

        # Bind keys to the window
        master.bind('s', self.stop)
        master.bind('S', self.stop)
        master.bind('r', self.resume)
        master.bind('R', self.resume)

        # Pendulum constants
        self.g = 1
        self.theta_i = radians(20)

        # Initialize time t to 0
        self.t = 0
        cord, bob = self.calcPendulum()
        self.cord = self.create_line(*cord, tags='cord')
        self.bob = self.create_oval(*bob, fill='red', tags='bob')

        # Start animation
        self.timer = ''
        self.speed = 1/50           # initial frequency of oscillation
        self.animate()

    def calcPendulum(self):
        # Angle of the pendulum (from the vertial) at time t
        L = 4*self.winfo_height()/5
        theta = self.theta_i * cos(sqrt(self.g/L) * self.t)
        # The two ends of the cord
        x_i, y_i = self.winfo_width()/2, self.winfo_height()/10
        x_f, y_f = x_i + L*sin(theta), y_i + L*cos(theta)
        rad = min(self.winfo_width(), self.winfo_height())/20
        cord_pos = x_i, y_i, x_f, y_f
        bob_pos = x_f - rad, y_f - rad, x_f + rad, y_f + rad
        return cord_pos, bob_pos

    def animate(self):
        cord, bob = self.calcPendulum()
        self.coords(self.cord, *cord)
        self.coords(self.bob, *bob)
        self.t += 2
        self.timer = self.after(int(1/self.speed), self.animate)

    def stop(self, event=None):
        self.after_cancel(self.timer)

    def resume(self, event=None):
        self.stop() # in case it's currently running, stop it
        self.animate()

def main():
    # Create a window
    win = Tk()
    win.title('Pendulum')
    part = SimplePendulum(win, width=200, height=300)
    part.pack(fill=BOTH, expand=True)
    win.mainloop() # Start the event loop

if __name__ == '__main__':
    main()
现在你可以像我一样在一个小演示程序中使用你的新部件,或者将它打包到任何更大的程序中。或者多次使用它

我还移动了其他一些东西,比如将长度计算放在计时步骤中,这样您就可以调整窗口的大小,钟摆也可以调整大小。你的数学很有趣,因为它很好地演示了钟摆长度和频率之间的关系。嗯,现在我们有了一个整洁的小部件,我们可以通过在屏幕上放一个短的和一个高的来轻松演示:

def main():
    # Create a window
    win = Tk()
    win.title('Pendulum')
    part = SimplePendulum(win, width=200, height=100)
    part.pack(side=LEFT)
    part = SimplePendulum(win, width=400, height=600)
    part.pack(side=LEFT)
    win.mainloop() # Start the event loop

哦我懂了。谢谢你的帮助!哦我懂了。谢谢你的帮助!令人惊叹的谢谢你的改进!令人惊叹的谢谢你的改进!