跳转至

§4.1 基础动画

字数 1546 个   代码 121 行   图片 18 张   阅读时间 7 分钟   访问量

一、动画简述

1.1 动画的实现方式

在 tkt 中,有两种实现动画的方式,一种是使用 tkt 封装好的动画类,另外一种是使用 tkinter 原生的方式。很显然,这里推荐使用 tkt 封装好的动画类。

1.1.1 tkt 方式

使用 tkt 封装的好的动画类既可以极大地简化实现动画的操作,还可以获得一个较好的动画效果。首先,tkt 有一个动画基类,这个动画基类的功能比较强大,可以通过继承它来实现各种各样的动画子类。

此外,tkt 还实现了对动画过程的细微控制,而不是一个简单的开始和结束。说简单点,就是可以通过一个控制函数来控制动画运动的过程。比如说,一个小球从左移动到右这一动画,我们可以通过使用控制函数来实现小球平滑移动这一效果,又或者是回弹的效果,而不是简单的平移。

除了上述说的平滑移动,还有其它很多的方式,只要你设置了合适的控制函数,动画就能按照控制函数那样去运动!

1.1.2 tk 原生方式

当然,说到底 tkt 封装的动画类也是基于 tk 原生动画方式实现的,但要直接使用 tk 原生的方式来做出一些动画有一定难度,且不好操控。

下面是一个简单的示例,该示例可以将画布上的一个小球向右平移:

import tkintertools as tkt

root = tkt.Tk()
cv = tkt.Canvas(root)
cv.place(width=1280, height=720)

ball = cv.create_oval(100, 100, 200, 200, fill="orange", outline="")


def animation(t: int = 0) -> None:
    if t < 100:
        cv.move(ball, 10, 0)
        cv.after(10, animation, t+1)


root.after(1000, animation)
root.mainloop()

效果如下:(1)

  1. 注:gif 图限制了帧率为 30 ,实际效果会更流畅一些,后面的同理。
图1 tk 原生动画

虽然使用 tk 原生的方式能实现一定的动画效果,但要再想深入实现某些功能就比较复杂了。由于 tk 实现动画的方式并不属于本教程的范畴,这里就不深入讲解了。

1.2 控制函数

从前面讲述的内容来看,想必你已经大致了解什么是控制函数了。本小节并不会对控制函数做一个详细的说明,这部分会出现在后续章节中。但此处仍指出 tkt 内置的 3 个控制函数:

  • flat: 平移运动,动画类控制函数的默认值都是它
  • smooth: 平滑运动
  • rebound: 回弹运动

下面是它们的效果,红绿蓝分别对应上述的三种控制函数:

图2 tkt 动画

下面是上述效果的实现代码:

import tkintertools as tkt
import tkintertools.animation as animation

root = tkt.Tk()
cv = tkt.Canvas(root)
cv.place(width=1280, height=720)

ball_1 = cv.create_oval(100, 100, 200, 200, fill="red", outline="")
ball_2 = cv.create_oval(100, 100+200, 200, 200+200, fill="green", outline="")
ball_3 = cv.create_oval(100, 100+400, 200, 200+400, fill="royalblue", outline="")

animation.MoveItem(cv, ball_1, 1000, (840, 0), fps=60, controller=animation.flat).start(delay=1000)
animation.MoveItem(cv, ball_2, 1000, (840, 0), fps=60, controller=animation.smooth).start(delay=1000)
animation.MoveItem(cv, ball_3, 1000, (840, 0), fps=60, controller=animation.rebound).start(delay=1000)

root.mainloop()

此外,还可以自定义控制函数,这部分会在后续内容中详细讲解。

二、动画类

这里说的动画类是动画基类的各个子类,关于动画基类的讲解在后续章节中。

每个动画子类都是对于某一特定动画的具体实现,使用时应该根据需求选取,若没有满足需要的动画子类,也可以基于动画基类继承后实现属于自己的动画子类,即自定义动画类。关于自定义动画类的内容将在后续章节中详述。

2.1 MoveTkWidget

MoveTkWidget 用来移动 tkinter 中的原生控件(如 tk 的 Label)及其继承的子类(如 tkt 的 Canvas)。

下面是一个简单的示例,利用该类移动 tk 的 Label 控件:

import tkinter

import tkintertools as tkt
import tkintertools.animation as animation

root = tkt.Tk()

label = tkinter.Label(root, text="TkLabel")
label.place(x=100, y=100)

animation.MoveTkWidget(label, 1000, (1000, 0), fps=60).start(delay=1000)

root.mainloop()

效果如下:

图3 移动 tk 原生控件

2.2 MoveWidget

MoveWidget 用来移动 tkt 中的控件。

使用方式和上面的类似,下面是一个简单的示例:

import tkintertools as tkt
import tkintertools.animation as animation

root = tkt.Tk()
cv = tkt.Canvas(root)
cv.place(width=1280, height=720)

label = tkt.Label(cv, (100, 100), text="Label")

animation.MoveWidget(label, 1000, (1000, 0), fps=60).start(delay=1000)

root.mainloop()

效果如下:

图4 移动控件

2.3 MoveComponent

MoveComponent 用来移动 tkt 中的组件。这里说的组件与上面说的控件有很大的区别,前面章节并没有详细地指出这个问题,这个问题将在后续关于自定义控件和组件的部分详细说明。

下面是一个简单示例:

import tkintertools as tkt
import tkintertools.animation as animation

root = tkt.Tk()
cv = tkt.Canvas(root)
cv.place(width=1280, height=720)

label = tkt.Label(cv, (100, 100), text="Label")

animation.MoveComponent(label.components[0], 1000, (1000, 0), fps=60).start(delay=1000)

root.mainloop()

效果如下:

图5 移动组件

可以看见,控件的形状和其它部分离了,但这里只是为了演示这个作用和效果,实际中一般并不会这样做。(没事干嘛把形状分离??

2.4 MoveItem

MoveItem 用来移动画布中的基本元素。

与其它不同的是,操作 item 必须指明其所属的画布,不然无法对 item 进行操作(1)。下面是一个简单的示例:

  1. 因为 item 实际只是一个 int 类型的对象,自身无法存储其所属画布的相关信息
import tkintertools as tkt
import tkintertools.animation as animation

root = tkt.Tk()
cv = tkt.Canvas(root)
cv.place(width=1280, height=720)

item = cv.create_rectangle(100, 100, 200, 200, fill="gray", outline="")

animation.MoveItem(cv, item, 1000, (1000, 0), fps=60).start(delay=1000)

root.mainloop()

效果如下:

图6 移动画布元素

2.5 GradientTkWidget

GradientTkWidget 用来使 tkinter 中的原生控件的相关颜色渐变。

下面是一个简单的示例:

import tkinter

import tkintertools as tkt
import tkintertools.animation as animation

root = tkt.Tk()

label = tkinter.Label(root, text="TkLabel")
label.place(x=200, y=200)

animation.GradientTkWidget(label, "bg", 2000, ("red", "green"), repeat=-1).start()

root.mainloop()

效果如下:

图7 tk 控件的渐变颜色

这个示例使用了 repeat 参数来让这个动画循环进行,repeat 是循环的次数,当次数小于 0 时会无限循环,直到动画类或者程序被终止。

2.6 GradientItem

GradientItem 用来使画布中元素的相关颜色渐变。

前面的渐变动画使用了循环,但每次循环之间没有过渡,看起来十分不自然。我们可以给动画类设定一个控制函数来让渐变颜色的起始值和终止值相同,以保证过渡自然。下面是一个简单的示例:

import math

import tkintertools as tkt
import tkintertools.animation as animation

root = tkt.Tk()
cv = tkt.Canvas(root)
cv.place(width=1280, height=720)

item = cv.create_rectangle(100, 100, 200, 200, outline="")

animation.GradientItem(cv, item, "fill", 2000, ("yellow", "purple"), repeat=-1, controller=lambda x: math.sin(x*math.pi)).start()

root.mainloop()

效果与呼吸灯类似:

图8 画布元素的渐变颜色

2.7 ScaleFontSize

ScaleFontSize 用来缩放组件 Text 的字体大小,注意不是控件 Text 的字体大小。

为了让字体的缩放看着更有张力,我们可以试着使用前面提到的 rebound 控制函数,下面是一个简单的示例:

import tkintertools as tkt
import tkintertools.animation as animation

root = tkt.Tk()
cv = tkt.Canvas(root)
cv.place(width=1280, height=720)

text = tkt.Text(cv, (100, 100), text="Text", anchor="center")

animation.ScaleFontSize(text.components[0], 500, (20, 48), controller=animation.rebound).start(delay=1000)

root.mainloop()

运行上述代码后,字体放大后会再回弹缩小,效果如下:

图9 tk 控件的渐变颜色

特别注意:慎用 ScaleFontSize

该类没有对窗口和画布的缩放做适配,在窗口或者画布缩放之后,该类可能会产生意想不到的效果,该效果一般不符合预期要求。