5

Python GUI教程(十五):在PyQt5中使用动画

 2 years ago
source link: https://zmister.com/archives/418.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

QT作为一个全面的桌面应用程序开发包,其自然提供了对图像的动画支持。本篇文章中,就来简单地在PYQt5中使用Animation动画功能。

本篇将会依次完成以下功能:

  • 在GUI界面中显示一个图片(用一个足球做演示);
  • 点击按钮实现足球的直线射门动画;
  • 点击按钮实现足球的曲线射门动画;

文章目录 [显示]

一、在图形界面显示图片的两种方法

一般情况下,想要在GUI中显示图片,我们会通过:
– 实例化一个QLable()部件;
– 实例化一个QPixmap()图形类;
– 通过QLabel()部件的setPixmap()方法设置QLabel()部件的图形;

就像如下代码所示:

# coding:utf-8

from PyQt5 import QtGui,QtWidgets
import sys

class MainUi(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("动画使用-zmister.com") # 设置窗口标题
        self.resize(400,200) # 规定窗口大小
        self.main_widget = QtWidgets.QWidget() # 创建一个widget部件
        self.label = QtWidgets.QLabel(self.main_widget) # 创建一个文本标签部件
        png = QtGui.QPixmap() # 创建一个绘图类
        png.load("logo.png") # 从pngz中加载一个图片
        self.label.setPixmap(png) # 设置文本标签的图形
        self.setCentralWidget(self.main_widget)

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    gui = MainUi()
    gui.show()
    sys.exit(app.exec_())

运行上述代码,我们将会得到如下所示的图形界面:
2018-08-18_212846.png

但是这种方法没有办法实现对图片更多的控制。为了更好地对图形界面中的图片进行控制和管理,我们还需要使用到其他的类,比如QtWidgets中的QGraphicsScene类,QGraphicsScene提供了一个场景,用于对2D图形进行管理。同时,为了展示QGraphicsScene中的内容,我们还需要使用到QtWidgets中的QGraphicsView类来提供一个视图部件。

下面,我们就通过一个简单的例子来了解一下QGraphicsScene类和QGraphicsView类的使用。

首先是完整的代码,如下所示:

# coding:utf-8

from PyQt5 import QtGui,QtWidgets,QtCore
import sys

class MainUi(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("动画使用-zmister.com") # 设置窗口标题
        self.resize(400,200) # 规定窗口大小
        self.main_widget = QtWidgets.QWidget() # 创建一个widget部件

        self.grapview = QtWidgets.QGraphicsView(self.main_widget) # 创建一个图形视图,继承自main_widget
        self.grapview.setGeometry(QtCore.QRect(10, 10, 380, 180)) # 设置图形视图的矩形区域
        self.scene = QtWidgets.QGraphicsScene() # 创建一个图形管理场景
        self.grapview.setScene(self.scene)
        png = QtGui.QPixmap() # 创建一个绘图类
        png.load("logo.png") # 从png中加载一个图片
        item = QtWidgets.QGraphicsPixmapItem(png) #
        self.scene.addItem(item)

        self.setCentralWidget(self.main_widget)

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    gui = MainUi()
    gui.show()
    sys.exit(app.exec_())

在这里面,基础的窗口代码与之前的代码类似,有区别且最核心的为以下几行代码。

首先,我们实例化创建了一个用于展示图形场景的图形视图QGraphicsView(),将它继承自self.main_widget主窗口部件:

self.grapview = QtWidgets.QGraphicsView(self.main_widget)

然后,我们实例化创建了一个图形场景管理类QGraphicsScene(),并通过setScene()方法将图形视图self.grapview的图形场景设置为刚刚实例化创建的QGraphicsScene():

self.scene = QtWidgets.QGraphicsScene()
self.grapview.setScene(self.scene)

最后,我们通过QPixmap()创建并加载了一个图片,将其添加到图形项目QGraphicsPixmapItem()中,并通过addItem()方法将图形项目添加到图形场景管理self.scene中:

png = QtGui.QPixmap()
png.load("logo.png")
item = QtWidgets.QGraphicsPixmapItem(png)
self.scene.addItem(item)

如此,我们就完成了通过QGraphicsView()类和QGraphicsScene()类在图形界面(GUI)中展示图片的功能,运行完整的代码,其显示出来的图形界面程序如下图所示:

2018-08-19_085453.png

二、几行代码射个门

上面我们通过两种不同的方式实现了图片在图形界面中的展示,接下来,我们借助QtCore中的QPropertyAnimation()来实现图片的动画效果。

QPropertyAnimation()类主要依靠操纵对象的QT属性来实现动画效果,其有几个比较主要的方法:

  • start():用于启动动画;
  • stop():用于停止动画;
  • setStartValue():用于设置动画的起始值;
  • setEndValue():用于设置动画的结束值;
  • setDuration():用于设置动画的持续时间;
  • setKeyValueAt():用于在特定时间创建一个关键的动画帧动作;
  • setLoopCount():用于设置动画的循环次数;

下面我们就使用QPropertyAnimation()类实现图片的动画。为了动画的效果比5毛特效要好一点,州的先生(公众号:zmister2016)在阿里的图标库iconfont里找了一个小足球和球门的图标,嗯,就像这样:

2018-08-19_091303-300x211.png

2018-08-19_105922-300x211.png

然后,我们创建一个图形界面,里面包含一个按钮、一个小球和一个球门的图片:

# coding:utf-8
from PyQt5 import QtGui,QtWidgets,QtCore
import sys

class MainUi(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("动画使用-zmister.com") # 设置窗口标题
        self.resize(400,200) # 规定窗口大小
        self.main_widget = QtWidgets.QWidget() # 创建一个widget部件
        self.button = QtWidgets.QPushButton('射门',self.main_widget) # 创建一个按钮
        self.button.setGeometry(10,10,60,30) # 设置按钮位置
        self.label = QtWidgets.QLabel(self.main_widget) # 创建一个文本标签部件用于显示足球
        self.label.setGeometry(50,80,50,50) # 设置足球位置
        png = QtGui.QPixmap()  # 创建一个绘图类
        png.load("football.png")  # 从png中加载一个图片
        self.label.setPixmap(png)  # 设置文本标签的图形
        self.label.setScaledContents(True)

        self.qiumen = QtWidgets.QLabel(self.main_widget)  # 创建一个文本标签部件用于显示球门
        self.qiumen.setGeometry(345, 75, 50, 50)  # 设置球门位置
        pngqiumen = QtGui.QPixmap()  # 创建一个绘图类
        pngqiumen.load("qiumen.png")  # 从png中加载一个图片
        self.qiumen.setPixmap(pngqiumen)  # 设置文本标签的图形

        self.setCentralWidget(self.main_widget)

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    gui = MainUi()
    gui.show()
    sys.exit(app.exec_())

运行上述代码,我们会得到一个如下图所示的图形界面:

2018-08-19_110209.png

我们的目的是想让图形界面中的小足球通过按钮控制,进入到球门中。接下来,我们就通过QPropertyAnimation()类来实现这个效果。

在MainUi()中新建一个名为shoot()的方法,在其中写入以下代码:

self.anim = QtCore.QPropertyAnimation(self.label,b'geometry') # 设置动画的对象及其属性
self.anim.setDuration(2000) # 设置动画间隔时间
self.anim.setStartValue(QtCore.QRect(50,80,50,50)) # 设置动画对象的起始属性
self.anim.setEndValue(QtCore.QRect(360, 90, 10, 10)) # 设置动画对象的结束属性
self.anim.start() # 启动动画

最后,我们再讲按钮的点击信号绑定到shoot()方法上:

self.button.clicked.connect(self.shoot)

完整的代码如下所示:

# coding:utf-8
from PyQt5 import QtGui,QtWidgets,QtCore
import sys

class MainUi(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("动画使用-zmister.com") # 设置窗口标题
        self.resize(400,200) # 规定窗口大小
        self.main_widget = QtWidgets.QWidget() # 创建一个widget部件
        self.button = QtWidgets.QPushButton('射门',self.main_widget) # 创建一个按钮
        self.button.setGeometry(10,10,60,30) # 设置按钮位置
        self.button.clicked.connect(self.shoot)
        self.label = QtWidgets.QLabel(self.main_widget) # 创建一个文本标签部件用于显示足球
        self.label.setGeometry(50,80,50,50) # 设置足球位置
        png = QtGui.QPixmap()  # 创建一个绘图类
        png.load("football.png")  # 从png中加载一个图片
        self.label.setPixmap(png)  # 设置文本标签的图形
        self.label.setScaledContents(True) # 图片随文本部件的大小变动

        self.qiumen = QtWidgets.QLabel(self.main_widget)  # 创建一个文本标签部件用于显示球门
        self.qiumen.setGeometry(345, 75, 50, 50)  # 设置球门位置
        pngqiumen = QtGui.QPixmap()  # 创建一个绘图类
        pngqiumen.load("qiumen.png")  # 从png中加载一个图片
        self.qiumen.setPixmap(pngqiumen)  # 设置文本标签的图形

        self.setCentralWidget(self.main_widget)

    def shoot(self):
        self.anim = QtCore.QPropertyAnimation(self.label,b'geometry') # 设置动画的对象及其属性
        self.anim.setDuration(2000) # 设置动画间隔时间
        self.anim.setStartValue(QtCore.QRect(50,80,50,50)) # 设置动画对象的起始属性
        self.anim.setEndValue(QtCore.QRect(360, 90, 10, 10)) # 设置动画对象的结束属性
        self.anim.start() # 启动动画

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    gui = MainUi()
    gui.show()
    sys.exit(app.exec_())

运行上述代码,点击“射门”按钮,我们将会得到如下动图所示的动画:

shoot.gif

这样,通过QPropertyAnimation()的setDuration()方法、setStartValue()方法、setEndValue()方法我们就实现了一个简单的动画。

三、圆月弯刀继续射门

但是上面的射门动画是一条直线将小足球移动到了球门之内,简单粗暴欠缺了些许美感,下面,我们让这个射门换一种方式,用圆月弯刀的香蕉球将小足球射入球门。

还记得上面我们提过QPropertyAnimation()的setKeyValueAt()这个用于设置动画关键帧的方法。现在我们就将利用它来实现足球射门时的曲线。

与上面的图形界面的代码不一样的是,我们需要绘制一条曲线线条来作为足球射门时的路径。所以我们需要对上面的图形界面的代码进行一些修改:

# coding:utf-8
from PyQt5 import QtGui,QtWidgets,QtCore
import sys

class MainUi(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("动画使用-州的先生zmister.com") # 设置窗口标题
        self.resize(400,200) # 规定窗口大小
        self.main_widget = QtWidgets.QWidget() # 创建一个widget部件
        self.button = QtWidgets.QPushButton('射门',self.main_widget) # 创建一个按钮
        self.button.setGeometry(10,10,60,30) # 设置按钮位置

        self.label = QtWidgets.QLabel(self.main_widget) # 创建一个文本标签部件用于显示足球
        self.label.setGeometry(50,150,50,50) # 设置足球位置
        png = QtGui.QPixmap()  # 创建一个绘图类
        png.load("football.png")  # 从png中加载一个图片
        self.label.setPixmap(png)  # 设置文本标签的图形
        self.label.setScaledContents(True) # 图片随文本部件的大小变动

        self.qiumen = QtWidgets.QLabel(self.main_widget)  # 创建一个文本标签部件用于显示球门
        self.qiumen.setGeometry(345, 75, 50, 50)  # 设置球门位置
        pngqiumen = QtGui.QPixmap()  # 创建一个绘图类
        pngqiumen.load("qiumen.png")  # 从png中加载一个图片
        self.qiumen.setPixmap(pngqiumen)  # 设置文本标签的图形

        self.path = QtGui.QPainterPath() # 实例化一个绘制类,用于绘制动作
        self.path.moveTo(50, 150)
        self.path.cubicTo(50, 150, 50, 20, 370, 90)

        self.setCentralWidget(self.main_widget)

    # 重写patintEvent()方法
    def paintEvent(self, e):
        qp = QtGui.QPainter()
        qp.begin(self)
        qp.drawPath(self.path) # 在图形界面上根据self.path绘制一条线条
        qp.end()

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    gui = MainUi()
    gui.show()
    sys.exit(app.exec_())

在上面的代码中,与之前的程序代码有一下不同之处:

我们实例化创建了一个QtGui.QPainterPath(),用于进行绘制操作。通过它的moveTo()方法,设置了绘制的起始点,通过它的cubicTo()方法,设置的绘制的整个路径:

self.path = QtGui.QPainterPath() # 实例化一个绘制类,用于绘制动作
self.path.moveTo(50, 150)
self.path.cubicTo(50, 150, 50, 20, 370, 90)

接着,我们重写了窗口的paintEvent()方法,根据绘制操作的定义,在图形上绘制一条相应的线条:

def paintEvent(self, e):
    qp = QtGui.QPainter()
    qp.begin(self)
    qp.drawPath(self.path) # 在图形界面上根据self.path绘制一条线条
    qp.end()

这样,我们的图形界面程序呈现出来的就是如下图所示的样子:

2018-08-19_133230.png

图形上呈现了我们设置的足球将要运动的轨迹,接下来,我们通过QPropertyAnimation()的setKeyValueAt()设置关键帧的路径,来实现足球曲线射门,具体操作同样在shoot()方法中进行:

def shoot(self):
    self.anim_x = QtCore.QPropertyAnimation(self.label, b'geometry')                    self.anim_x.setDuration(3000)
    self.anim_x.setStartValue(QtCore.QRect(50,150,50,50)) # 设置动画对象的起始属性
    positionValues = [n / 10  for n in range(0, 10)]
    for n,i in enumerate(positionValues):
        x = self.path.pointAtPercent(i).x()
        y = self.path.pointAtPercent(i).y()
        z =  50 - n*3.5
        self.anim_x.setKeyValueAt(i,QtCore.QRect(x, y,z,z))
        self.anim_x.setEndValue(QtCore.QRect(360, 90, 10, 10))
        self.anim_x.start()

最后,同样将“射门”按钮的点击信号绑定在shoot()方法上。运行程序代码,点击“射门”按钮,将会出现如下动图所示的动画效果:

quxian_shoot.gif

为了更加的美观,其实可以将重写的paintEvent()去掉,在这里为了演示路径,就没有去除。

在PyQt5中使用动画是不是很简单?有问题欢迎留言讨论。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK