警告

本节包含从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或其子类。这是必要的,因为动画框架依赖于元对象系统来获取关于它正在动画的对象的全部信息。

复杂的动画可以通过构建QAbstractAnimation的树结构来构造,其中树是一个包含其他动画的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 signal上更改它,否则不会更改任何值。

该框架允许您对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的pos Qt属性,使其在10秒(10000毫秒)内从屏幕的左上角移动到结束位置(250, 250)。

它使用线性插值方法来控制动画在开始值和结束值之间的速度。尝试在开始值和结束值之间添加另一个值,看看它们是如何插值的。这次使用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属性的QObject的值,如果该值有一个setter方法。在这种情况下,从包含该值的类派生一个新类,并使用setter为该值添加一个Qt属性。

注意

每个Qt属性也需要一个getter,因此如果未定义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的属性系统

动画与图形视图框架

QPropertyAnimation 也可以用于动画化一个不继承 QObject 的 QGraphicsItem。在这种情况下,你需要从你想要动画化的图形项派生一个类。这个派生类也应该继承自 QObject,以便在 QGraphicsItem 上使用 QPropertyAnimation。以下示例展示了如何实现这一点:

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

注意

你也可以从QGraphicsWidget派生,它已经是一个QObject

如前一节所述,您需要定义要动画化的属性。派生类必须首先从QObject继承,因为元对象系统需要它。

缓动曲线

一个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()

在这个例子中,动画遵循一条曲线,使button像球一样弹跳。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 按顺序播放其动画。它在前一个动画完成后开始列表中的下一个动画。

一个组本身就是一个动画,因此你可以将其添加到另一个组中。这样,就可以构建一个动画树,定义动画之间相互播放的时间关系。

对象所有权

一个 QPropertyAnimation 应该始终有一个控制其生命周期的父对象。一个典型的应用程序可能包括几个分组的动画,其中动画组拥有这些动画的所有权。一个独立的 QPropertyAnimation 必须显式地分配一个父对象来控制其生命周期。在下面的示例中,你可以看到一个独立的 QPropertyAnimation 将 QApplication 实例作为其父对象:

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来控制动画的生命周期。