警告
本节包含从C++自动翻译到Python的代码片段,可能包含错误。
动画框架¶
动画框架概述
动画框架提供了一种简单的方式来为您的GUI元素添加动画效果。它使您能够为小部件或QObject
的Qt属性值添加动画。该框架提供的大多数功能在Qt Quick中也可用,在Qt Quick中可以以声明方式定义动画。
本概述解释了框架的架构,并通过示例展示了用于动画化QObject
和GUI元素的常见技术。
动画架构¶
下图展示了框架提供的最重要的类:
它包括QAbstractAnimation
类,该类为动画提供了必要的基础。这个类定义了框架支持的所有动画的通用属性。例如,启动、停止和暂停动画的能力。该类还接收时间变化的通知。
该框架进一步提供了QVariantAnimation
和QAnimationGroup
类,这些类基于它们的基础案例QAbstractAnimation
。在层次结构中的下一个是QPropertyAnimation
,它派生自QVariantAnimation,并允许您对小部件或QObject
的Qt属性进行动画处理。该类使用缓动曲线对属性值进行插值。有了这些,您只需要一个具有可以动画化的Qt属性值的QObject
类。
复杂的动画可以通过构建QAbstractAnimation
的树结构来构造,其中树是一个包含其他动画的QAnimationGroup
。这些动画组也可以包含代表不同组或动画的子组,例如QParallelAnimationGroup
和QSequentialAnimationGroup
。
在幕后,所有动画都由一个全局计时器控制,该计时器发送有关所有正在运行的动画的更新
。
有关这些单独类及其在框架中的角色的详细信息,请参阅它们的文档。
框架提供的类¶
这些类提供了创建简单和复杂动画所需的基础设施。
QAbstractAnimation 类是所有动画的基类。
QAnimationGroup 类是动画组的抽象基类。
QParallelAnimationGroup 类提供了一个并行动画组。
QPauseAnimation 类为 QSequentialAnimationGroup 提供了一个暂停功能。
QPropertyAnimation 类用于动画化 Qt 属性。
QSequentialAnimationGroup 类提供了一个顺序的动画组。
QVariantAnimation 类为动画提供了一个基类。
QEasingCurve 类提供了用于控制动画的缓动曲线。
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
的子类 — QSequentialAnimationGroup
和 QParallelAnimationGroup
— 是其他动画的容器,以便这些动画可以按顺序或并行播放。QAnimationGroup
不会动画化属性,但它会定期收到时间变化的通知。这使得它能够将这些时间变化转发给动画组,从而控制动画的播放时间。
以下两个示例展示了如何使用 QSequentialAnimationGroup
和 QParallelAnimationGroup
:
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
来控制动画的生命周期。