警告

本节包含自动从C++转换为Python的代码片段,可能包含错误。

动画框架#

动画框架的概述

动画框架提供了一种简单的方法来动画化您的GUI元素。它使您能够动画化小部件或QObject的Qt属性值。该框架提供的大部分功能在Qt Quick中也可用,在Qt Quick中可以声明式地定义动画。

本概述解释了框架的架构,并使用示例来演示用于动画化QObject和GUI元素的一些常用技术。

动画架构#

以下图表显示了框架提供的最重要的类

../_images/animations-architecture.png

包括QAbstractAnimation类,该类为动画提供必要的起点。该类定义了框架支持的所有动画的通用属性。例如,开始、停止和暂停动画的能力。该类还接收时间改变通知。

该框架进一步提供了QVariantAnimationQAnimationGroup类,它们建立在它们的基类QAbstractAnimation之上。在层次结构中下一个是QPropertyAnimation,它源自QVariantAnimation,并允许您动画化小部件或QObject的Qt属性。该类使用缓动曲线对属性值进行插值。这样,您只需要一个具有您要动画化的Qt属性值的QObject类。

注意

必须动画的目标对象是QObject或其子类。这是必要的,因为动画框架依赖于元对象系统来获取关于它动画的所有对象信息。

可以通过构建树形结构的方式来构建复杂动画,其中树形结构是由包含其他动画的QAnimationGroup组成。这些动画组还可以包含代表不同组或动画的子组,例如QParallelAnimationGroupQSequentialAnimationGroup

在幕后,所有动画都由一个全局计时器控制,该计时器发送有关所有运行动画的更新

有关这些个别类及其在框架中角色的详细信息,请参阅其文档。

框架提供的类#

这些类为创建简单和复杂动画提供了必要的基础设施。

PySide6.QtCore.QAbstractAnimation

QAbstractAnimation类是所有动画的基础。

PySide6.QtCore.QAnimationGroup

QAnimationGroup类是动画组的抽象基类。

PySide6.QtCore.QParallelAnimationGroup

QParallelAnimationGroup类提供了并行动画组。

PySide6.QtCore.QPauseAnimation

QPauseAnimation类为QSequentialAnimationGroup提供了暂停功能。

PySide6.QtCore.QPropertyAnimation

QPropertyAnimation类用于动画Qt属性。

PySide6.QtCore.QSequentialAnimationGroup

QSequentialAnimationGroup类提供了序列动画组。

PySide6.QtCore.QVariantAnimation

QVariantAnimation类为动画提供基类。

PySide6.QtCore.QEasingCurve

QEasingCurve类为动画控制提供缓动曲线。

PySide6.QtCore.QTimeLine

QTimeLine类为控制动画提供时间线。

动画Qt属性#

由于QPropertyAnimation类可以在Qt属性上插值,因此它经常被使用。实际上,它的超类QVariantAnimation提供了一个updateCurrentValue()的抽象实现,除非你在valueChanged信号处更改它,否则不会更改任何值。

此框架允许您对Qt中现有类的Qt属性进行动画处理。例如,QWidget类可以嵌入到QGraphicsView中,具有边界、颜色等属性。以下示例演示了如何对QPushButton小部件进行动画处理。

from PySide6.QtWidgets import QApplication
from PySide6.QtWidgets import QPushButton
from PySide6.QtCore import QPropertyAnimation
class MyButtonWidget(QWidget):

# public
    MyButtonWidget(QWidget parent = None)

def __init__(self, QWidget(parent):

    button = QPushButton(tr("Animated Button"), self)
    anim = QPropertyAnimation(button, "pos", self)
    anim.setDuration(10000)
    anim.setStartValue(QPoint(0, 0))
    anim.setEndValue(QPoint(100, 250))
    anim.start()

if __name__ == "__main__":

    a = QApplication(argc, argv)
    buttonAnimWidget = MyButtonWidget()
    buttonAnimWidget.resize(QSize(800, 600))
    buttonAnimWidget.show()
    return a.exec()

该示例对QPushButton的Qt属性pos进行动画处理,将其从屏幕的左上角移动到终点(250, 250),耗时10秒(10000毫秒)。

它使用线性插值法来控制动画在起始值和结束值之间的速度。尝试在起始值和结束值之间添加另一个值,以查看它们如何进行插值。这次使用QPropertyAnimation::setKeyValueAt函数添加这些值。

...
anim->setDuration(10000);
anim->setKeyValueAt(0, QPoint(0, 0));
anim->setKeyValueAt(0.8, QPoint(250, 250));
anim->setKeyValueAt(1, QPoint(0, 0));
...

在这个例子中,动画在8秒内将按钮移动到(250, 250),然后在剩下的2秒内将其移回到原始位置。按钮的运动在这两点之间进行线性插值。

如果某个值未声明为Qt属性但有setter方法,您还可以对其动画处理。在这种情况下,您应从包含该值的类派生一个新类,并为该值添加一个带有setter的Qt属性。

注意

每个Qt属性也都需要一个getter,因此如果您没有定义它,您应该提供一个getter。

class MyGraphicsRectItem : public QObject, public QGraphicsRectItem
{
    Q_OBJECT
    Q_PROPERTY(QPointF pos READ pos WRITE setPos)
};

在这个例子中,MyGraphicsRectItem从QGraphicsRectItem和QObject派生出来,并定义了pos属性。即使QGraphicsRectItem没有提供pos属性,您也可以对该项的pos进行动画处理。

有关Qt属性系统的详细介绍,请参见Qt的属性系统

动画和图形视图框架Link to this heading

QPropertyAnimation 还可以用于对不是从QObject继承的自定义QGraphicsItem进行动画处理。在这种情况下,您应从您希望进行动画处理的图形项派生一个新类。该派生类还应继承QObject,以便可以在QGraphicsItem上使用QPropertyAnimation。以下示例展示了如何执行此操作。

class Pixmap : public QObject, public QGraphicsPixmapItem
{
    Q_OBJECT
    Q_PROPERTY(QPointF pos READ pos WRITE setPos)
    ...
}

注意

您还可以从QObject已经是从QObject派生的QGraphicsWidget进行派生。

如前所述,您需要定义要动画处理的属性。派生类必须首先继承QObject,因为元对象系统需要这样。

缓动曲线Link to this heading

QPropertyAnimation 进行起点和终点属性值之间的线性插值。除了向动画中添加更多关键值,你还可以选择一个缓动曲线来控制0和1之间插值的速度,而无需改变路径。

def __init__(self, QWidget(parent):

    button = QPushButton(tr("Animated Button"), self)
    anim = QPropertyAnimation(button, "pos", self)
    anim.setDuration(10000)
    anim.setStartValue(QPoint(0, 0))
    anim.setEndValue(QPoint(100, 250))
    anim.setEasingCurve(QEasingCurve.OutBounce)
    anim.start()

在这个例子中,动画遵循的曲线让按钮像球一样弹跳。 QEasingCurve 提供了许多曲线以供选择,这些曲线来自 Type 枚举。如果您想使用不可用的其他曲线,可以自己实现它并使用 QEasingCurve 进行注册。

组合动画

应用程序通常包含多个动画。例如,它希望同时移动多个图形项,或者在每个动画项之后按顺序移动。

QAnimationGroup 的子类 —— QSequentialAnimationGroupQParallelAnimationGroup — 是其他动画的容器,这样可以使得这些动画既可以顺序也可以并行地动画化。 QAnimationGroup 不直接对属性进行动画化,但它会定期收到时间变化的通知。这使得它可以将这些时间变化转移到动画组,并控制其动画何时播放。

以下两个示例分别演示了使用 QSequentialAnimationGroupQParallelAnimationGroup 的用法。

def __init__(self, QWidget(parent):

    bonnie = QPushButton(tr("Bonnie"), self)
    clyde = QPushButton(tr("Clyde"), self)
    anim1 = QPropertyAnimation(bonnie, "pos", self)
    anim1.setDuration(3000)
    anim1.setStartValue(QPoint(0, 0))
    anim1.setEndValue(QPoint(100, 250))
    anim2 = QPropertyAnimation(clyde, "pos", self)
    anim2.setDuration(3000)
    anim2.setStartValue(QPoint(100, 250))
    anim2.setEndValue(QPoint(500, 500))
    parallelAnim = QParallelAnimationGroup()
    parallelAnim.addAnimation(anim1)
    parallelAnim.addAnimation(anim2)
    parallelAnim.start()

并行组同时播放多个动画。其 start() 函数开始播放组中所有的动画。

def __init__(self, QWidget(parent):

    bonnie = QPushButton(tr("Bonnie"), self)
    clyde = QPushButton(tr("Clyde"), self)
    anim1 = QPropertyAnimation(bonnie, "pos", self)
    anim1.setDuration(3000)
    anim1.setStartValue(QPoint(0, 0))
    anim1.setEndValue(QPoint(100, 250))
    anim2 = QPropertyAnimation(clyde, "pos", self)
    anim2.setDuration(3000)
    anim2.setStartValue(QPoint(0, 0))
    anim2.setEndValue(QPoint(200, 250))
    sequenceAnim = QSequentialAnimationGroup()
    sequenceAnim.addAnimation(anim1)
    sequenceAnim.addAnimation(anim2)
    sequenceAnim.start()

如名称所示,QSequentialAnimationGroup 顺序播放其动画。在之前的动画完成后,它将启动列表中的下一个动画。

组本身就是一个动画,因此您可以将它添加到另一个组中。这样就可以构建一个动画树,用于定义动画相对于彼此的播放时间。

对象所有权

from PySide6.QtWidgets import QApplication
from PySide6.QtWidgets import QPushButton
from PySide6.QtCore import QPropertyAnimation
class MyButtonWidget(QWidget):

# public
    MyButtonWidget(QWidget parent = None)

def __init__(self, QWidget(parent):

    button = QPushButton(tr("Animated Button"), self)
    anim = QPropertyAnimation(button, "pos", self)
    anim.setDuration(10000)
    anim.setStartValue(QPoint(0, 0))
    anim.setEndValue(QPoint(100, 250))
    anim.start()

if __name__ == "__main__":

    a = QApplication(argc, argv)
    buttonAnimWidget = MyButtonWidget()
    buttonAnimWidget.resize(QSize(800, 600))
    buttonAnimWidget.show()
    return a.exec()

注意

您还可以在启动动画时选择 delete policy 来控制动画的生命周期。