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

时间:2018-01-22 02:20:47

标签: python python-3.x animation tkinter

我正在研究tkinter中一个简单钟摆的动画。我已将 s r 绑定到动画的stopresume窗口。但是我注意到一个奇怪的怪癖我无法解释:如果我没有停止钟摆时按 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()

3 个答案:

答案 0 :(得分:2)

与您的问题没有关系,但如果更新画布上的元素而不是清除整个画布并从头开始重绘,则动画会更顺畅:

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类设为Canvas:

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

答案 1 :(得分:1)

只需将resume替换为:

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

resume运行self.isStopped时,如果animate标志检查,它运行一个新的animate,它有自己的自身递归递归,因此它调用线性增加{{1}每次resume调用没有标志的方法。

答案 2 :(得分:1)

或者,您可以使用after_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()