毕竟国内大部分程序员看英文费劲,有人给翻译出来了,特学习一下。

这篇文章主要讲述 Qt 动画框架是如何架构的,并且通过最简单的例子展示如何实现 Qt 动画。

概述

下面这张图片展示了 Qt 动画框架中最重要的类以及它们之间的关系。

Qt 动画框架的最底层是由 ​​QAbstractAnimation​​​ 类和它两个子类 ​​QVariantAnimation​​​、​​QVariantAnimation​​​ 构成。​​QAbstractAnimation​​ 是所有动画的基类,它实现了所有动画类的基本属性。明显的,它能够开始、停止和暂停一个动画,它也能接收时间改变的通知。

Qt 动画框架提供了一个 ​​QPropertyAnimation​​​ 类,他继承自 ​​QVariantAnimation​​​ 并且执行Qt属性动画(是Qt元对象系统“meta-object system”的一部分)。​​QPropertyAnimation​​ 类可以通过缓动曲线来改写某一对象的属性。当你想动态改变对象(对象的基类必须是QObject)的某一个值时,你可以通过设置它的属性(property)来实现。

复杂的动画可以通过建立一个 ​​QAbstractAnimation​​​ 的树来构建,这个树使用 ​​QAnimationGroup​​​ 来构建, ​​QAnimationGroup​​​ 作为其它动画的容器。还需要注意这个动画组也是 ​​QAbstractAnimation​​ 类的子类,所以这个动画组也能够包含其它动画组。

Qt 动画框架能够单独使用,也可以被设计为状态机的一部分。状态机提供了一个用来播放动画的特定的状态。​​QState​​​ 当进入或退出某个状态时能够设置属性,并且这个特定的动画状态将在指定 ​​QPropertyAnimation​​ 时给予的值之间做插值运算。稍后我们将近一步介绍此问题。

在场景的幕后,动画被一个全局定时器控制,这个定时器发送 updates 到所有的正在播放的动画中。

想要获取动画类的详细信息,请自行查看Qt帮助文档(Qt助手)。

动画框架类

下面这些类提供了一个用于创建既简单又复杂的动画框架。

QAbstractAnimation

所有动画的基础

QAnimationGroup

动画组的抽象基类

QParallelAnimationGroup

动画的并行组

QPauseAnimation

用于暂停QSequentialAnimationGroup

QPropertyAnimation

Qt动画属性

QSequentialAnimationGroup

动画的串行组

QVariantAnimation

动画的抽象基类

QEasingCurve

控制动画的缓动曲线类

QTimeLine

控制动画的时间轴类


动态的Qt属性

正如在上面的章节中提到的那样,​​QPropertyAnimation​​​ 类能够修改Qt属性,要动态的改变一个值(​​QObject​​​ 的某个属性)就应该使用这个类。事实上,他是 ​​QVariantAnimation​​(一个抽象类,不能被直接使用)的一个超类。

我们选择使用动态的Qt属性的一个主要的原因是它能够使我们自由的使用动画在 Qt API 中已经存在的类中。尤其是 ​​QWidget​​​ 类(也能嵌入到 ​​QGraphicsView​​ 类中)具有很大属性,例如它的边界(geometry),颜色等等。

让我们看一个小例子。

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();

这段代码在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 property)。唯一的要求是,此值具有一个 ​​setter​​​,然后,您可以继承包含该值的类,并声明使用此 ​​setter​​​ 属性。请注意,每个 Qt 的属性需要一个 ​​getter​​​,所以,如果它没有被定义的话,你需要自己提供一个 ​​getter​​。

class MyGraphicsRectItem : public QObject, public QGraphicsRectItem
{
Q_OBJECT
Q_PROPERTY(QRectF geometry READ geometry WRITE setGeometry)
};

正如上一节所述,我们需要定义我们所希望的动画属性。

需要注意的是为了满足元对象系统的需求,​​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​​。


串行动画和并行动画

一个应用程序通常包含多个动画。例如,您可能希望同时移动多个图形或将它们按顺序排列。

子类 ​​QAnimationGroup​​​(​​QSequentialAnimationGroup​​​ 和 ​​QParallelAnimationGroup​​​)可以包含其他的动画,使得这些动画可以串行或者并行移动。 ​​QAnimationGroup​​ 类不是动画属性,是一个抽象动画类,但是它能够得到时间周期性变化的通知。这使它能够捕获其包含的动画的时间变化,从而控制何时播放动画。

让我们来看看使用 ​​QSequentialAnimationGroup​​​ 和 ​​QParallelAnimationGroup​​ 类的代码示例。

QPushButton *bonnie = new QPushButton("Bonnie", this);
bonnie->show();

QPushButton *clyde = new QPushButton("Clyde", this);
clyde->show();

QPropertyAnimation *anim1 = new QPropertyAnimation(bonnie, "geometry");
// Set up anim1
anim1->setDuration(3000);
anim1->setStartValue(QRect(0, 0, 100, 30));
anim1->setEndValue(QRect(250, 250, 100, 30));

QPropertyAnimation *anim2 = new QPropertyAnimation(clyde, "geometry");
// Set up anim2
anim2->setDuration(3000);
anim2->setStartValue(QRect(250, 250, 100, 30));
anim2->setEndValue(QRect(0, 0, 100, 30));

QParallelAnimationGroup *group = new QParallelAnimationGroup;
group->addAnimation(anim1);
group->addAnimation(anim2);

group->start();

并行组能够同时播放多个动画,调用它的start()函数将开启它控制的所有的动画。运行结果如下图。

QPushButton *bonnie = new QPushButton("Bonnie", this);
bonnie->show();

QPushButton *clyde = new QPushButton("Clyde", this);
clyde->show();

QPropertyAnimation *anim1 = new QPropertyAnimation(bonnie, "geometry");
// Set up anim1
anim1->setDuration(3000);
anim1->setStartValue(QRect(0, 0, 100, 30));
anim1->setEndValue(QRect(250, 250, 100, 30));

QPropertyAnimation *anim2 = new QPropertyAnimation(clyde, "geometry");
// Set up anim2
anim2->setDuration(3000);
anim2->setStartValue(QRect(250, 250, 100, 30));
anim2->setEndValue(QRect(0, 0, 100, 30));

QSequentialAnimationGroup *group = new QSequentialAnimationGroup;
group->addAnimation(anim1);
group->addAnimation(anim2);

group->start();

毫无疑问你已经猜到了,​​QSequentialAnimationGroup​​ 顺序的播放动画。它在前一个动画结束之后再开启下一个动画。运行效果如下图。

由于动画组本身也是一个动画,您可以将其添加到另一个动画组中。通过这种方式,你可以创建一个指定动画性对于另一个动画何时播放的树形结构的动画。


动画和状态机

当使用一个状态机时,我们可以将一个或多个动画使用 ​​QSignalTransition​​​ 或 ​​QEventTransition​​​ 类通过状态之间的转换联系起来。这些类均源自 ​​QAbstractTransition​​​ ,它定义了便利函数 ​​addAnimation()​​,允许一个或多个动画在进行转移时触发。

我们还有可能必须将属性与状态关联起来而不是,而不是设置他们的最终值。下面是一套完整的演示​​QPushButton​​​ 的 ​​geometry​​ 属性的动画例子。

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();

运行结果如下图。

想要获取如何使用状态机动画框架的更加详细的例子,请查看states example (在 examples/animation/states 目录下)。

本文翻译自Qt帮助文档The Animation Framework,因本人水平有限,不足之处望大家批评改正。