Tkinter的PNG图片“点击”动画

问题描述

我正在尝试使用具有三个“状态”的PNG图像创建“自定义按钮”:

  1. 标准闲置图片(green_button.png)
  2. 悬停图像(green_button_hover.png)
  3. 点击动画(6帧)

我发现gif动画无法在Tkinter中轻松显示,因此我决定使用“自定义解决方案”-将按钮动画分为6个不同的帧(PNG图像-从green_button_click_0.png到green_button_click_5.png)并显示一点一点地延迟。不幸的是,它无法正常工作,我尝试了许多变体,但似乎没有任何解决办法。我唯一了解到的是,我不能使用time.sleep()在帧之间等待,因为它将冻结应用程序并且不显示任何内容

将鼠标悬停在<Enter><Leave>上可以正常运行,但是动画在单击时不起作用。有人可以给我一些建议吗?

这是我的代码的摘录(我是一名Python初学者,所以可能很废话,对此感到抱歉):

# Green button deFinition:
    green_button_image = ImageTk.PhotoImage(file = "images/buttons/green_button.png")
    self.PlayerCanvas.create_image(green_button_x,green_button_y,image = green_button_image,tags = ('green_hover'))
    self.PlayerCanvas.image = green_button_image
    self.PlayerCanvas.tag_bind('green_hover','<Enter>',self.green_hover)


def green_hover(self,event):
    green_button_image = ImageTk.PhotoImage(file = 'images/buttons/green_button_hover.png')
    self.PlayerCanvas.create_image(green_button_x,tags = ('green_leave','green_click'))
    self.PlayerCanvas.image = green_button_image
    self.PlayerCanvas.tag_bind('green_leave','<Leave>',self.green_leave)
    self.PlayerCanvas.tag_bind('green_click','<Button-1>',self.green_click)

def green_leave(self,event):
    green_button_image = ImageTk.PhotoImage(file = 'images/buttons/green_button.png')
    self.PlayerCanvas.create_image(green_button_x,tags = 'green_hover')
    self.PlayerCanvas.image = green_button_image

def green_click(self,event):
    frames = 5
    frame = 0
    while frame <= frames:
        self.green_animation(frame,frames)
        frame += 1
    else:
        green_button_image = ImageTk.PhotoImage(file = 'images/buttons/green_button.png')
        self.PlayerCanvas.create_image(green_button_x,tags = 'green_hover')
        self.PlayerCanvas.image = green_button_image

def green_animation(self,frame,frames):
    green_button_image = ImageTk.PhotoImage(file = 'images/buttons/green_button_click_' + str(frame) + '.png')
    self.PlayerCanvas.create_image(green_button_x,image = green_button_image)
    self.PlayerCanvas.image = green_button_image

解决方法

如注释中所建议,您可以使用.after(<delay in ms>,<callback>,*args for the callback)来重复更改用作按钮的图像。它是画布上的一个项目,因此可以使用

进行更改
canvas.itemconfigure(<item id or tag>,image=<new image>)

点击后,启动动画self.green_animation(0),操作如下:

def green_animation(self,index):
    self.canvas.itemconfigure(self.green_button,image=self.anim[index])
    if index < len(self.anim) - 1: # it is not the last image,schedule next image change
        self.after(1000,self.green_animation,index + 1)

最后,我做了一些更复杂的操作,以避免在输入/离开动画和单击动画之间以及在动画结束之前重新单击按钮时发生冲突:

  • 我使用布尔值self.anim_on来了解动画是否正在运行,并在情况允许的情况下防止进入/离开外观发生变化。
  • 我将时间表回调的ID存储在self.anim_after_id中,以便在用户再次单击该按钮时将其取消,以免同时运行两次动画。

这是一个完整的示例,更改按钮颜色而不是图像,以便任何人都可以轻松运行它:

import tkinter as tk

class App(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.canvas = tk.Canvas(self)
        self.canvas.pack()

        self.green_button = self.canvas.create_oval(50,50,100,fill='green',tags='button') # button
        self.anim = ['red','orange','yellow','cyan','blue','green']  # replace image sequence by colors for the example
        self.anim_on = False
        self.anim_after_id = None

        self.canvas.tag_bind('button','<Enter>',self.green_enter)
        self.canvas.tag_bind('button','<Leave>',self.green_leave)
        self.canvas.tag_bind('button','<ButtonRelease-1>',self.green_click)

    def green_enter(self,event):
        if self.anim_on:
            return
        self.canvas.itemconfigure(self.green_button,fill='light green')

    def green_leave(self,fill='green')

    def green_click(self,event):
        # cancel previous anim if running
        try:
            self.after_cancel(self.anim_after_id)
        except ValueError:
            pass
        self.anim_on = True
        self.green_animation(0)

    def green_animation(self,index):
        self.canvas.itemconfigure(self.green_button,fill=self.anim[index])
        if index < len(self.anim) - 1:
            self.anim_after_id = self.after(1000,index + 1)
        else:
            self.anim_on = False

App().mainloop()

要使其适应图像,只需更改:

  • create_oval-> create_image
  • self.anim = [image1,...]
  • fill=...-> image=...

screencast

包含图像但不继承自tk.Tk的代码:

import Tkinter as tk  #  python2
from PIL import ImageTk

class App:
    def __init__(self,the_window):
        self.window = the_window
        self.canvas = tk.Canvas(the_window)
        self.canvas.pack()
        self.img = ImageTk.PhotoImage(file='images/buttons/green_button.png')
        self.img_hover = ImageTk.PhotoImage(file='images/buttons/green_button_hover.png')

        self.green_button = self.canvas.create_image(50,image=self.img,tags='button') # button
        self.anim = [ImageTk.PhotoImage(file='images/buttons/green_button_click_%i.png' % i) for i in range(5)]
        self.anim.append(self.img)

        self.anim_on = False
        self.anim_after_id = None

        self.canvas.tag_bind('button',image=self.img_hover)

    def green_leave(self,image=self.img)

    def green_click(self,event):
        # cancel previous anim if running
        try:
            self.window.after_cancel(self.anim_after_id)
        except ValueError:
            pass
        self.anim_on = True
        self.green_animation(0)

    def green_animation(self,image=self.anim[index])
        if index < len(self.anim) - 1:
            self.anim_after_id = self.window.after(1000,index + 1)
        else:
            self.anim_on = False

my_window = tk.Tk()
app = App(my_window)
my_window.mainloop()