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



我正在制作tkinter中简单钟摆的动画。我已将sr绑定到窗口以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()

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

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

只需将resume替换为:

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

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

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

最新更新