§4.1 基础动画
字数 1559 个 代码 121 行 图片 18 张 阅读时间 7 分钟 访问量
一、动画简述
1.1 动画的实现方式
在 maliang
中,有两种实现动画的方式,一种是使用 maliang
封装好的动画类,另外一种是使用 tkinter
原生的方式。很显然,这里推荐使用 maliang
封装好的动画类。
1.1.1 maliang
方式
使用 maliang
封装的好的动画类既可以极大地简化实现动画的操作,还可以获得一个较好的动画效果。首先,maliang
有一个动画基类,这个动画基类的功能比较强大,可以通过继承它来实现各种各样的动画子类。
此外,maliang
还实现了对动画过程的细微控制,而不是一个简单的开始和结束。说简单点,就是可以通过一个控制函数来控制动画运动的过程。比如说,一个小球从左移动到右这一动画,我们可以通过使用控制函数来实现小球平滑移动这一效果,又或者是回弹的效果,而不是简单的平移。
除了上述说的平滑移动,还有其它很多的方式,只要你设置了合适的控制函数,动画就能按照控制函数那样去运动!
1.1.2 tkinter
原生方式
当然,说到底 maliang
封装的动画类也是基于 tkinter
原生动画方式实现的,但要直接使用 tkinter
原生的方式来做出一些动画有一定难度,且不好操控。
下面是一个简单的示例,该示例可以将画布上的一个小球向右平移:
| import maliang
root = maliang.Tk()
cv = maliang.Canvas()
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)
- 注:gif 图限制了帧率为 30 ,实际效果会更流畅一些,后面的同理。
虽然使用 tkinter
原生的方式能实现一定的动画效果,但要再想深入实现某些功能就比较复杂了。由于 tkinter
实现动画的方式并不属于本教程的范畴,这里就不深入讲解了。
1.2 控制函数
从前面讲述的内容来看,想必你已经大致了解什么是控制函数了。本小节并不会对控制函数做一个详细的说明,这部分会出现在后续章节中。但此处仍指出 maliang
内置的 5 个控制函数:
下面是它们的效果,红绿蓝分别对应上述的前三种控制函数:
下面是上述效果的实现代码:
| import maliang
import maliang.animation as animation
root = maliang.Tk()
cv = maliang.Canvas()
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, (840, 0), 1000, fps=60, controller=animation.linear).start(delay=1000)
animation.MoveItem(cv, ball_2, (840, 0), 1000, fps=60, controller=animation.smooth).start(delay=1000)
animation.MoveItem(cv, ball_3, (840, 0), 1000, fps=60, controller=animation.rebound).start(delay=1000)
root.mainloop()
|
此外,还可以自定义控制函数,这部分会在后续内容中详细讲解。
二、动画类
这里说的动画类是动画基类的各个子类,关于动画基类的讲解在后续章节中。
每个动画子类都是对于某一特定动画的具体实现,使用时应该根据需求选取,若没有满足需要的动画子类,也可以基于动画基类继承后实现属于自己的动画子类,即自定义动画类。关于自定义动画类的内容将在后续章节中详述。
MoveTkWidget
用来移动 tkinter
中的原生控件(如 tkinter
的 Label
)及其继承的子类(如 maliang
的 Canvas
)。
下面是一个简单的示例,利用该类移动 tkinter
的 Label
控件:
| import tkinter
import maliang
import maliang.animation as animation
root = maliang.Tk()
label = tkinter.Label(root, text="TkLabel")
label.place(x=100, y=100)
animation.MoveTkWidget(label, (1000, 0), 1000, fps=60).start(delay=1000)
root.mainloop()
|
效果如下:
MoveWidget
用来移动 maliang
中的控件。
使用方式和上面的类似,下面是一个简单的示例:
| import maliang
import maliang.animation as animation
root = maliang.Tk()
cv = maliang.Canvas()
cv.place(width=1280, height=720)
label = maliang.Label(cv, (100, 100), text="Label")
animation.MoveWidget(label, (1000, 0), 1000, fps=60).start(delay=1000)
root.mainloop()
|
效果如下:
MoveElement
用来移动 maliang
中的元素。这里说的元素与上面说的控件有很大的区别,前面章节并没有详细地指出这个问题,这个问题将在后续关于自定义控件和元素的部分详细说明。
下面是一个简单示例:
| import maliang
import maliang.animation as animation
root = maliang.Tk()
cv = maliang.Canvas(root)
cv.place(width=1280, height=720)
label = maliang.Label(cv, (100, 100), text="Label")
animation.MoveElement(label.elements[0], (1000, 0), 1000, fps=60).start(delay=1000)
root.mainloop()
|
效果如下:
可以看见,控件的形状和其它部分离了,但这里只是为了演示这个作用和效果,实际中一般并不会这样做。(没事干嘛把形状分离??)
MoveItem
用来移动画布中的基本项。
与其它不同的是,操作 item 必须指明其所属的画布,不然无法对 item 进行操作(1)。下面是一个简单的示例:
- 因为 item 实际只是一个
int
类型的对象,自身无法存储其所属画布的相关信息
| import maliang
import maliang.animation as animation
root = maliang.Tk()
cv = maliang.Canvas()
cv.place(width=1280, height=720)
item = cv.create_rectangle(100, 100, 200, 200, fill="gray", outline="")
animation.MoveItem(cv, item, (1000, 0), 1000, fps=60).start(delay=1000)
root.mainloop()
|
效果如下:
GradientTkWidget
用来使 tkinter
中的原生控件的相关颜色渐变。
下面是一个简单的示例:
| import tkinter
import maliang
import maliang.animation as animation
root = maliang.Tk()
label = tkinter.Label(root, text="TkLabel")
label.place(x=200, y=200)
animation.GradientTkWidget(label, "bg",("red", "green"), 2000, repeat=-1).start()
root.mainloop()
|
效果如下:
这个示例使用了 repeat
参数来让这个动画循环进行,repeat
是循环的次数,当次数小于 0
时会无限循环,直到动画类或者程序被终止。
GradientItem
用来使画布中元素的相关颜色渐变。
前面的渐变动画使用了循环,但每次循环之间没有过渡,看起来十分不自然。我们可以给动画类设定一个控制函数来让渐变颜色的起始值和终止值相同,以保证过渡自然。下面是一个简单的示例:
| import math
import maliang
import maliang.animation as animation
root = maliang.Tk()
cv = maliang.Canvas()
cv.place(width=1280, height=720)
item = cv.create_rectangle(100, 100, 200, 200, outline="")
animation.GradientItem(cv, item, "fill", ("yellow", "purple"), 2000, repeat=-1, controller=lambda x: math.sin(x*math.pi)).start()
root.mainloop()
|
效果与呼吸灯类似:
ScaleFontSize
用来缩放组件 Text
的字体大小,注意不是控件 Text
的字体大小。
为了让字体的缩放看着更有张力,我们可以试着使用前面提到的 rebound
控制函数,下面是一个简单的示例:
| import maliang
import maliang.animation as animation
root = maliang.Tk()
cv = maliang.Canvas()
cv.place(width=1280, height=720)
text = maliang.Text(cv, (100, 100), text="Text", anchor="center")
animation.ScaleFontSize(text.texts[0], (20, 48), 500, controller=animation.rebound).start(delay=1000)
root.mainloop()
|
运行上述代码后,字体放大后会再回弹缩小,效果如下:
特别注意:慎用 ScaleFontSize
类
该类没有对窗口和画布的缩放做适配,在窗口或者画布缩放之后,该类可能会产生意想不到的效果,该效果一般不符合预期要求。