动画框架旨在为创建动画和平滑GUI提供一种简单的方法。通过动画Qt属性,框架为动画控件和其他QObject提供很大的自由。框架也可以用于Graphics View 框架。在动画框架中看到的一些概念也能在Qt Quick中看到,它提供了声明的方式定义动画。动画框架的大部分知识可以应用于Qt Quick。
在这个概述中,我们解释了它的架构基础。我们还展示了框架允许动画QObjects和图形项目的最常见技术的例子。
The Animation Architecture
在本节,我们将介绍动画框架的架构以及如何动画Qt属性。下图展示了动画框架中最重要的类。
动画框架基础由基类QAbstractAnimation以及它的两个子类QVariantAnimation和QAnimationGroup组成。QAbstractAnimation是所有动画的祖先。它实现了框架中动画所有公用的基础属性;尤其是启动,停止和暂停动画功能。它也接收时间变化通知。
动画框架进一步提供QPropertyAnimation类,它是继承自QVariantAnimation类并且实现Qt属性(Qt属性是Qt元对象系统的一部分)的动画。类使用缓动曲线对动画插值。所以当你想要动画一个值,你可以将它声明为一个属性并让你的类成为一个QObject。注意这让我们在动画已经存在的控件和其它QObject时有更多的自由度。
复杂动画可以通过建造一个QAbstractAnimation的树结构来构建。通过QAnimationGroup建造树,QAnimationGroup功能是作为其他动画的容器。注意组也是QAbstractAnimation的子类,所以组自己也可以包含其他组。
动画框架可以单独使用,但也被设计为状态机框架的一部分(有关Qt状态机的介绍,请参阅状态机框架)。状态机提供了一个可以播放动画的特殊状态。QState也可以在状态进入或退出时设置属性,当给定QPropertyAnimation时,这个特殊的动画状态将在这些值之间插入。稍后我们将更详细地讨论这个问题。
在后台,动画由全局计时器控制,它向正在播放的所有动画发送更新。
关于类在框架中的功能和角色的详细描述,请查阅它们的类描述。
Classes in the Animation Framework
这些类为创建简单和复杂动画提供框架。
所有动画基础 | |
动画组的抽象基类 | |
并行动画组 | |
串行动画组的暂停 | |
动画Qt属性 | |
串行动画组 | |
动画的基类 | |
控制动画的缓动曲线 | |
控制动画的时间线 |
Animating Qt Properties
像前面提到的,QPropertyAnimation类可以对Qt属性插值。通常这个类应该用于值的动画;事实上,它的超类QVariantAnimation有一个updateCurrentValue()的空实现,并且不会改变任何值,除非我们自己在valueChanged信号上改变它。
我们选择动画Qt属性的主要原因是它为我们提供了在Qt API中动画已经存在的类的自由。尤其是QWidget类(我们也可以在QGraphicsView中嵌入它)有它的边界,颜色等属性。让我们看一个小例子:
QPushButton button("Animated Button");
button.show();
QPropertyAnimation animation(&button, "geometry");
animation.setDuration(10000);
animation.setStartValue(QRect(0, 0, 100, 30));
animation.setEndValue(QRect(250, 250, 100, 30));
animation.start();
这个代码将移动button,在10秒内从屏幕的左上角到位置(250,250)。
上面的例子实现了从起始和结束值之间的线性插值。也可以设置位于起始和结束值之间的值。然后插值将会经过这些点:
QPushButton button("Animated Button");
button.show();
QPropertyAnimation animation(&button, "geometry");
animation.setDuration(10000);
animation.setKeyValueAt(0, QRect(0, 0, 100, 30));
animation.setKeyValueAt(0.8, QRect(250, 250, 100, 30));
animation.setKeyValueAt(1, QRect(0, 0, 100, 30));
animation.start();
在这个例子中,动画将在8秒内将按钮移动到(250,250)点,然后在剩余2秒内将它移回起始位置。这些点间的运动是线性插值的。
你也可以动画一个QObject未声明为Qt属性的值。唯一的要求是这个值有一个设置器。然后,您可以创建包含该值的类的子类,并声明一个使用该setter的属性。注意每个Qt属性要求获取器,所以如果没有定义的话你需要提供一个获取器。
class MyGraphicsRectItem : public QObject, public QGraphicsRectItem
{
Q_OBJECT
Q_PROPERTY(QRectF geometry READ geometry WRITE setGeometry)
};
上面的代码例子中,我们子类QGraphicsRectItem并定义一个几何属性。现在即使QGraphicsRectItem没有提供几何属性,我们也可以动画控件几何。
关于Qt属性系统的一般介绍,参考它的概述。
Animations and the Graphics View Framework
当你想要动画QGraphicsItem,你也可以使用QPropertiAnimation。然而,QGraphicsItem不是继承自QObject。一个好的解决方法是子类化你想要动画的图形单元。然后这个类也要继承QObject。这种方式下,QPropertyAnimation可以用于QGraphicsItem。下面的例子展示如何做。另一中方法是继承自QGraphicsWidget,它本就是QObject。
class Pixmap : public QObject, public QGraphicsPixmapItem
{
Q_OBJECT
Q_PROPERTY(QPointF pos READ pos WRITE setPos)
...
像前面描述的,我们需要定义我们期望动画的属性。
注意QObject必须是第一个继承类因为元对象系统要求这样。
Easing Curves
像前面提到的,QPropertyAnimation在起始点和结束点之间执行插值。除了为动画插入更多关键值之外,你也可以使用缓动曲线。缓动曲线描述一个功能,它控制在0-1之间插值应该以怎样的速度进行移动,并且它是在不改变插值路径的情况下控制动画速度。
QPushButton button("Animated Button");
button.show();
QPropertyAnimation animation(&button, "geometry");
animation.setDuration(3000);
animation.setStartValue(QRect(0, 0, 100, 30));
animation.setEndValue(QRect(250, 250, 100, 30));
animation.setEasingCurve(QEasingCurve::OutBounce);
animation.start();
在这里,动画将遵循一条曲线,使它像一个球一样弹跳,就像是它从开始位置放下到结束的位置结束。QEasingCurve有聚集了大量曲线,它们由QEasingCurve::Type枚举定义。如果你需要其他曲线,你也可以自己实现一个,并用QEasingCurve注册它。
Putting Animations Together
一个应用通常包含不止一个动画。举个例子,你可能想要同时移动不止一个图形单元或者按顺序移动他们。
QAnimationGroup子类(QSequentialAnimationGroup和QParallelAnimationGroup)是其他动画的容器,这些动画可以串行也可以并行动画。QAnimationGroup是一个不动画属性的动画例子,但它会周期性地得到时间变化的通知。这使它能够将这些时间变化转发给它所包含的动画,从而控制什么时候播放动画。
让我们看一下同时使用QSequentialAnimationGroup和QParallelAnimationGroup的代码示例,从后者开始。
QPushButton *bonnie = new QPushButton("Bonnie");
bonnie->show();
QPushButton *clyde = new QPushButton("Clyde");
clyde->show();
QPropertyAnimation *anim1 = new QPropertyAnimation(bonnie, "geometry");
// Set up anim1
QPropertyAnimation *anim2 = new QPropertyAnimation(clyde, "geometry");
// Set up anim2
QParallelAnimationGroup *group = new QParallelAnimationGroup;
group->addAnimation(anim1);
group->addAnimation(anim2);
group->start();
一个并行组同时播放多个动画。调用它的start()函数将启动它所控制的所有动画。
QPushButton button("Animated Button");
button.show();
QPropertyAnimation anim1(&button, "geometry");
anim1.setDuration(3000);
anim1.setStartValue(QRect(0, 0, 100, 30));
anim1.setEndValue(QRect(500, 500, 100, 30));
QPropertyAnimation anim2(&button, "geometry");
anim2.setDuration(3000);
anim2.setStartValue(QRect(500, 500, 100, 30));
anim2.setEndValue(QRect(1000, 500, 100, 30));
QSequentialAnimationGroup group;
group.addAnimation(&anim1);
group.addAnimation(&anim2);
group.start();
你肯定已经猜到了,QSequentialAnimationGroup按顺序播放它的动画。它在前一个动画完成后开始列表中的下一个动画。
因为动画组本身就是一个动画,所以您可以将它添加到另一个组中。通过这种方式,您可以构建一个动画的树结构,它指定动画何时相对于其他动画播放。
Animations and States
当使用状态机时,我们可以使用QSignalTransition或QEventTransition类将一个或多个动画关联到状态之间的转换。这些类都派生自QAbstractTransition,它定义了方便的addAnimation()函数,该函数允许在发生转换时附加一个或多个触发的动画。
我们还可以将属性与状态关联起来,而不是自己设置开始和结束值。下面是一个完整的QPushButton几何动画代码示例。
QPushButton *button = new QPushButton("Animated Button");
button->show();
QStateMachine *machine = new QStateMachine;
QState *state1 = new QState(machine);
state1->assignProperty(button, "geometry", QRect(0, 0, 100, 30));
machine->setInitialState(state1);
QState *state2 = new QState(machine);
state2->assignProperty(button, "geometry", QRect(250, 250, 100, 30));
QSignalTransition *transition1 = state1->addTransition(button,
SIGNAL(clicked()), state2);
transition1->addAnimation(new QPropertyAnimation(button, "geometry"));
QSignalTransition *transition2 = state2->addTransition(button,
SIGNAL(clicked()), state1);
transition2->addAnimation(new QPropertyAnimation(button, "geometry"));
machine->start();
有关如何将状态机框架用于动画的更全面的示例,请参见状态示例(它位于examples/animation/states目录中)。