ZetCode

PyQt QPropertyAnimation

最后修改于 2023 年 8 月 24 日

在本文中,我们将展示如何使用 QPropertyAnimation 在 PyQt 中创建动画。在示例中,我们将对对象的大小、颜色和位置进行动画处理。

访问 Advanced PyQt5 电子书,阅读 PyQt6 教程,或列出所有 PyQt 教程

QPropertyAnimation

QPropertyAnimation 对 PyQt 属性进行插值。声明属性的类*必须*是 QObject

QPropertyAnimation 方法

下表显示了一些重要的 QPropertyAnimation 方法。

名称描述
start启动动画
stop终止动画
setStartValue设置动画的起始值
setEndValue设置动画的结束值
setDuration设置动画的持续时间,单位为毫秒
setKeyValueAt在给定的步骤创建具有给定值的关键帧
setLoopCount设置动画的重复次数

使用 QPropertyAnimation 对大小进行动画处理

在第一个示例中,我们对一个控件的大小进行动画处理。

size_anim.py
#!/usr/bin/python

from PyQt6.QtWidgets import QWidget, QApplication, QFrame, QPushButton
from PyQt6.QtCore import QRect, QPropertyAnimation
import sys

class Example(QWidget):

    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):

        self.button = QPushButton("Start", self)
        self.button.clicked.connect(self.doAnim)
        self.button.move(30, 30)

        self.frame = QFrame(self)
        self.frame.setFrameStyle(QFrame.Shape.Panel | QFrame.Shadow.Raised)
        self.frame.setGeometry(150, 30, 100, 100)

        self.setGeometry(300, 300, 380, 300)
        self.setWindowTitle('Animation')
        self.show()

    def doAnim(self):

        self.anim = QPropertyAnimation(self.frame, b"geometry")
        self.anim.setDuration(10000)
        self.anim.setStartValue(QRect(150, 30, 100, 100))
        self.anim.setEndValue(QRect(150, 30, 200, 200))
        self.anim.start()


def main():

    app = QApplication([])
    ex = Example()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()

该示例对 QFrame 控件的大小进行动画处理。

self.button = QPushButton("Start", self)
self.button.clicked.connect(self.doAnim)
self.button.move(30, 30)

动画通过一个 QPushButton 启动。

self.anim = QPropertyAnimation(self.frame, b"geometry")

创建了 QPropertyAnimation。第一个参数是要进行动画处理的目标对象;在我们的例子中,我们对一个 QFrame 控件进行动画处理。第二个参数是将要改变的属性。

self.anim.setDuration(10000)

setDuration 设置动画的持续时间,单位为毫秒。

self.anim.setStartValue(QRect(150, 30, 100, 100))
self.anim.setEndValue(QRect(150, 30, 200, 200))

通过 setStartValuesetEndValue,我们分别定义动画的起始值和结束值。

self.anim.start()

动画通过 start 方法开始。

使用 QPropertyAnimation 对颜色进行动画处理

下面的示例对一个控件的颜色进行动画处理。由于没有颜色属性,我们必须创建一个。

color_anim.py
#!/usr/bin/python


from PyQt6.QtWidgets import (QWidget, QApplication, QPushButton,
                             QLabel, QHBoxLayout, QSizePolicy)
from PyQt6.QtGui import QColor
from PyQt6.QtCore import QPropertyAnimation, pyqtProperty
import sys

class MyLabel(QLabel):

    def __init__(self, text):
        super().__init__(text)

    def _set_color(self, col):

        palette = self.palette()
        palette.setColor(self.foregroundRole(), col)
        self.setPalette(palette)

    color = pyqtProperty(QColor, fset=_set_color)


class Example(QWidget):

    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):

        hbox = QHBoxLayout(self)

        self.button = QPushButton("Start", self)
        self.button.setSizePolicy(QSizePolicy.Policy.Fixed,
                                  QSizePolicy.Policy.Fixed)
        hbox.addWidget(self.button)

        hbox.addSpacing(40)

        self.label = MyLabel("Summer")
        font = self.label.font()
        font.setPointSize(35)
        self.label.setFont(font)
        hbox.addWidget(self.label)

        self.anim = QPropertyAnimation(self.label, b"color")
        self.anim.setDuration(2500)
        self.anim.setLoopCount(2)
        self.anim.setStartValue(QColor(0, 0, 0))
        self.anim.setEndValue(QColor(0, 110, 150))

        self.button.clicked.connect(self.anim.start)

        self.setGeometry(300, 300, 380, 250)
        self.setWindowTitle('Color anim')
        self.show()


def main():

    app = QApplication([])
    ex = Example()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()

该示例逐渐改变 QLabel 的颜色值。

class MyLabel(QLabel):

    def __init__(self, text):
        super().__init__(text)

    def _set_color(self, col):

        palette = self.palette()
        palette.setColor(self.foregroundRole(), col)
        self.setPalette(palette)

    color = pyqtProperty(QColor, fset=_set_color)

QLabel 没有颜色属性;因此,我们用 pyqtProperty 定义了一个。改变这个属性会更新标签的颜色。

self.anim = QPropertyAnimation(self.label, b"color")

QPropertyAnimation 改变标签控件的 color 属性。

self.anim.setLoopCount(2)

通过 setLoopCount 方法,我们可以改变动画将运行多少次。

self.anim.setStartValue(QColor(0, 0, 0))
self.anim.setEndValue(QColor(0, 110, 150))

我们设置了起始和结束的颜色值。

使用 QPropertyAnimation 沿曲线动画

下面的示例使一个小球沿贝塞尔曲线动画。

anim_along_curve.py
#!/usr/bin/python

from PyQt6.QtWidgets import QApplication, QWidget, QLabel
from PyQt6.QtGui import QPainter, QPixmap, QPainterPath
from PyQt6.QtCore import QPoint, QPropertyAnimation, pyqtProperty
import sys


class Ball(QLabel):

    def __init__(self, parent):
        super().__init__(parent)

        pix = QPixmap("ball.png")
        self.h = pix.height()
        self.w = pix.width()

        self.setPixmap(pix)

    def _set_pos(self, pos):

        self.move(pos.x() - self.w//2, pos.y() - self.h//2)

    pos = pyqtProperty(QPoint, fset=_set_pos)


class Example(QWidget):

    def __init__(self):
        super().__init__()

        self.initView()
        self.initAnimation()

    def initView(self):

        self.path = QPainterPath()
        self.path.moveTo(30, 30)
        self.path.cubicTo(30, 30, 200, 350, 350, 30)

        self.ball = Ball(self)

        self.ball.pos = QPoint(30, 30)

        self.setWindowTitle("Animation along curve")
        self.setGeometry(300, 300, 400, 300)
        self.show()

    def paintEvent(self, e):

        qp = QPainter()
        qp.begin(self)
        qp.setRenderHint(QPainter.RenderHint.Antialiasing)
        qp.drawPath(self.path)
        qp.end()

    def initAnimation(self):

        self.anim = QPropertyAnimation(self.ball, b'pos')
        self.anim.setDuration(7000)

        self.anim.setStartValue(QPoint(30, 30))

        vals = [p/100 for p in range(0, 101)]

        for i in vals:
            self.anim.setKeyValueAt(i, self.path.pointAtPercent(i))

        self.anim.setEndValue(QPoint(350, 30))
        self.anim.start()


def main():

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec())


if __name__ == '__main__':
    main()

该示例在窗口上绘制一条曲线。它使一个小球对象沿着绘制的曲线进行动画。

class Ball(QLabel):

    def __init__(self, parent):
        super().__init__(parent)

        pix = QPixmap("ball.png")
        self.h = pix.height()
        self.w = pix.width()

        self.setPixmap(pix)

小球显示在一个 QLabel 控件中。

def _set_pos(self, pos):

    self.move(pos.x() - self.w//2, pos.y() - self.h//2)

pos = pyqtProperty(QPoint, fset=_set_pos)

我们调整小球的位置;我们希望将标签的中心放在曲线上。

self.path = QPainterPath()
self.path.moveTo(30, 30)
self.path.cubicTo(30, 30, 200, 350, 350, 30)

贝塞尔曲线是用 QPainterPath 创建的。它的 cubicTo 方法将起点、控制点和终点作为参数。

def paintEvent(self, e):

    qp = QPainter()
    qp.begin(self)
    qp.setRenderHint(QPainter.RenderHint.Antialiasing)
    qp.drawPath(self.path)
    qp.end()

曲线在 paintEvent 方法中用 drawPath 方法绘制。

self.anim = QPropertyAnimation(self.ball, b'pos')

我们使用 QPropertyAnimation 对小球的 pos 属性进行动画处理。

vals = [p/100 for p in range(0, 101)]

通过 Python 的列表推导式,我们创建了一个动画步骤的列表。这些步骤是介于 0 和 1 之间的值。

for i in vals:
    self.anim.setKeyValueAt(i, self.path.pointAtPercent(i))

通过 setKeyValueAt,我们定义了小球在给定步骤的位置。通过 pointAtPercent,我们得到路径上给定百分比处的 QPoint

Animation along curve
图:沿曲线动画

图形视图框架中的 QPropertyAnimation

QPropertyAnimation 可以在图形视图框架中对图形项进行动画处理。被动画处理的对象*必须*继承自 QObjectQGraphicsItem

gvf_anim.py
#!/usr/bin/python

from PyQt6.QtWidgets import (QApplication, QGraphicsView,
                             QGraphicsPixmapItem, QGraphicsScene)
from PyQt6.QtGui import QPainter, QPixmap
from PyQt6.QtCore import (QObject, QPointF, QPropertyAnimation, pyqtProperty)
import sys


class Ball(QObject):

    def __init__(self):
        super().__init__()

        self.pixmap_item = QGraphicsPixmapItem(QPixmap('ball.png'))

    def _set_pos(self, pos):
        self.pixmap_item.setPos(pos)

    pos = pyqtProperty(QPointF, fset=_set_pos)


class Example(QGraphicsView):

    def __init__(self):
        super().__init__()

        self.initView()

    def initView(self):

        self.ball = Ball()

        self.anim = QPropertyAnimation(self.ball, b'pos')
        self.anim.setDuration(8000)
        self.anim.setStartValue(QPointF(5, 30))

        self.anim.setKeyValueAt(0.3, QPointF(80, 30))
        self.anim.setKeyValueAt(0.5, QPointF(200, 30))
        self.anim.setKeyValueAt(0.8, QPointF(250, 250))

        self.anim.setEndValue(QPointF(290, 30))

        self.scene = QGraphicsScene(self)
        self.scene.setSceneRect(0, 0, 300, 300)
        self.scene.addItem(self.ball.pixmap_item)
        self.setScene(self.scene)

        self.setWindowTitle('Ball animation')
        self.setRenderHint(QPainter.RenderHint.Antialiasing)
        self.setGeometry(400, 300, 500, 350)

        self.anim.start()

        self.show()


def main():

    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec())


if __name__ == '__main__':
    main()

该示例在图形视图框架中使用 QPropertyAnimation 对一个小球对象进行动画处理。

class Ball(QObject):

    def __init__(self):
        super().__init__()

        self.pixmap_item = QGraphicsPixmapItem(QPixmap('ball.png'))

    def _set_pos(self, pos):
        self.pixmap_item.setPos(pos)

    pos = pyqtProperty(QPointF, fset=_set_pos)

由于 PyQt 不支持多重继承,我们使用组合技术来满足前面提到的条件。

class Example(QGraphicsView):

    def __init__(self):
        super().__init__()

        self.initView()

QGraphicsView 在一个可滚动的视口中可视化 QGraphicsScene 的内容。

self.anim = QPropertyAnimation(self.ball, b'pos')

我们将使用 QPropertyAnimation 对小球对象的位置属性进行动画处理。

self.anim.setDuration(8000)

动画持续八秒。

self.anim.setKeyValueAt(0.3, QPointF(80, 30))
self.anim.setKeyValueAt(0.5, QPointF(200, 30))
self.anim.setKeyValueAt(0.8, QPointF(250, 250))

通过 setKeyValueAt 方法,我们在给定的步骤创建具有给定值的关键帧。换句话说,我们定义了在动画的给定步骤中,小球位于何处。

self.scene = QGraphicsScene(self)
self.scene.setSceneRect(0, 0, 300, 300)
self.scene.addItem(self.ball.pixmap_item)

创建了 QGraphicsScene 并将小球添加到场景中。它提供了一个用于管理大量 2D 图形项的表面。请注意,我们将小球的属性添加到场景中,而不是小球对象本身。

在本文中,我们使用 QPropertyAnimation 创建了动画。

作者

我的名字是 Jan Bodnar,我是一名充满热情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。至今,我已经撰写了超过 1400 篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出所有 PyQt 教程