一、前言
动画框架旨在提供一种创建动画和平滑GUI的简便方法。 通过对Qt属性进行动画处理,框架为动画小部件和其他QObject提供了极大的自由度。 该框架也可以与Graphics View框架一起使用。 Qt Quick也提供了动画框架中可用的许多概念,其中提供了定义动画的声明性方式。 获得的有关动画框架的许多知识都可以应用于Qt Quick。
在本概述中,我们解释了其体系结构的基础。 我们还将展示该框架允许对QObject和图形项目进行动画处理的最常见技术的示例。
二、动画架构
在本节中,我们将对动画框架的体系结构以及如何将其用于Qt属性进行动画化进行深入研究。 下图显示了动画框架中最重要的类。
动画框架由基类QAbstractAnimation及其两个子类QVariantAnimation和QAnimationGroup组成。QAbstractAnimation是所有动画的祖先。它表示框架中所有动画通用的基本属性;值得注意的是,可以启动、停止和暂停动画的能力。它还接收时间更改通知。
动画框架进一步提供了QPropertyAnimation类,它继承QVariantAnimation并执行Qt属性的动画,该属性是Qt元对象系统的一部分。该类使用简化曲线在属性上执行插值。因此,当您想要动画一个值时,您可以将它声明为一个属性并使您的类成为QObject。注意,这给了我们很大的自由来动画已经存在的小部件和其他qobject。
通过构建QAbstractAnimations的树形结构,可以构建复杂的动画。树是使用QAnimationGroups构建的,它作为其他动画的容器。还要注意,组是QAbstractAnimation的子类,因此组本身可以包含其他组。
动画框架可以单独使用,但也被设计为状态机框架的一部分(关于Qt状态机的介绍,请参阅状态机框架)。状态机提供了可以播放动画的特殊状态。当状态被输入或退出时,QState也可以设置属性,并且当给定QPropertyAnimation时,这个特殊的动画状态将在这些值之间插入。稍后我们将对此进行更详细的讨论。
在幕后,动画由一个全局计时器控制,它向所有正在播放的动画发送更新。
有关类在框架中的功能和角色的详细描述,请查阅它们的类描述。
三、动画框架中的类
以下的类提供了创建简单和复杂动画的框架,继承关系见下图:
Class | 简介 |
---|---|
QAbstractAnimation | 所有动画的基础 |
QAnimationGroup | 动画组的抽象基类 |
QEasingCurve | 放松曲线来控制动画 |
QParallelAnimationGroup | 平行动画组 |
QPauseAnimation | 暂停QSequentialAnimationGroup |
QPropertyAnimation | 的Qt属性 |
QSequentialAnimationGroup | 动画序列组 |
QTimeLine | 控制动画的时间轴 |
QVariantAnimation | 动画的基类 |
四、动画Qt属性
如前一节所述,QPropertyAnimation类可以插入Qt属性。通常这个类应该用于值的动画;实际上,它的超类QVariantAnimation有一个空的updateCurrentValue()实现,并且不改变任何值,除非我们自己在valueChanged信号上改变它。
我们选择动画Qt属性的一个主要原因是,它让我们可以自由地动画Qt API中已经存在的类。值得注意的是,QWidget类(我们也可以嵌入到QGraphicsView中)具有其边界、颜色等属性。让我们看一个小例子:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPushButton button("Animated Button");
button.show();
QPropertyAnimation animation(&button, "pos");
animation.setDuration(3000);
animation.setStartValue(button.pos());
animation.setEndValue(button.pos() + QPoint(100,100));
QObject::connect(&button,&QPushButton::clicked,[&]{
animation.start();
});
a.exec();
return 0;
}
这段代码将在3秒(3000毫秒)内将按钮从当前位置移动到以当前位置为基准偏移(100,100)的地方。
上面的示例将在开始值和结束值之间进行线性插值。
还可以在开始值和结束值之间设置值。插值会经过这些点:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPushButton button("Animated Button");
button.show();
QPropertyAnimation animation(&button, "pos");
animation.setDuration(3000);
QPoint pos = button.pos();
animation.setKeyValueAt(0, pos);
animation.setKeyValueAt(0.8, pos + QPoint(100,100));
animation.setKeyValueAt(1, pos);
QObject::connect(&button,&QPushButton::clicked,[&]{
animation.start();
});
a.exec();
return 0;
}
在本例中,动画将在2.4秒内将按钮移动到偏移位置,然后在剩下的0.6秒内将其移动回原来的位置。运动将在这些点之间被线性插值。
您还可以对未声明为Qt属性的QObject的值进行动画处理。唯一的要求是这个值有一个setter。然后可以创建包含该值的类的子类,并声明使用此setter的属性。注意,每个Qt属性都需要一个getter,所以如果没有定义它,您需要自己提供一个getter。
class MyGraphicsRectItem : public QObject, public QGraphicsRectItem
{
Q_OBJECT
Q_PROPERTY(QRectF geometry READ geometry WRITE setGeometry)
};
在上面的代码示例中,我们子类化qgraphics整流并定义一个几何属性。现在,即使qgraphics整流tem没有提供几何属性,我们也可以对小部件的几何形状进行动画处理。
有关Qt属性系统的一般介绍,请参见概述。
五、动画和图形视图框架
要为QGraphicsItems设置动画时,还可以使用QPropertyAnimation。 但是,QGraphicsItem不继承QObject。 一个好的解决方案是将您要设置动画的图形项目子类化。 然后,此类还将继承QObject。 这样,QPropertyAnimation可以用于QGraphicsItems。 下面的示例显示了如何完成此操作。 另一种可能性是继承QGraphicsWidget,它已经是QObject。
class Pixmap : public QObject, public QGraphicsPixmapItem
{
Q_OBJECT
Q_PROPERTY(QPointF pos READ pos WRITE setPos)
...
如前一节所述,我们需要定义希望设置动画的属性。
请注意,因为元对象系统需要这样做,所以QObject必须是继承的第一类。
六、缓速曲线
如前所述,QPropertyAnimation在开始属性值和结束属性值之间执行一个插值。除了向动画中添加更多的键值之外,还可以使用缓动曲线。缓速曲线描述了一个控制0到1之间的插值速度的函数,如果您想在不改变插值路径的情况下控制动画的速度,那么缓速曲线非常有用。
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPushButton button("Animated Button");
button.show();
QPropertyAnimation animation(&button, "pos");
animation.setDuration(3000);
QPoint pos = button.pos();
animation.setKeyValueAt(0, pos);
animation.setKeyValueAt(1, pos + QPoint(100,100));
// animation.setKeyValueAt(1, pos);
animation.setEasingCurve(QEasingCurve::OutBounce);
QObject::connect(&button,&QPushButton::clicked,[&]{
animation.start();
});
a.exec();
return 0;
}
这里的动画将会遵循一条曲线,使它像一个球一样反弹,就好像它是从开始到结束的位置。QEasingCurve有大量的曲线供你选择。它们由QEasingCurve::Type enum定义。如果您需要另一个曲线,您也可以自己实现一个,并注册到QEasingCurve。
七、将动画放在一起
一个应用程序通常包含多个动画。 例如,您可能想同时移动多个图形项目,或者依次移动它们。
QAnimationGroup的子类(QSequentialAnimationGroup和QParallelAnimationGroup)是其他动画的容器,因此这些动画可以按顺序或并行进行动画处理。 QAnimationGroup是不对属性进行动画处理的动画示例,但是会定期收到时间更改通知。 这使它可以将这些时间更改转发到其包含的动画,从而控制何时播放其动画。
让我们看一下同时使用QSequentialAnimationGroup和QParallelAnimationGroup的代码示例。
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
QPushButton *bonnie = new QPushButton("Bonnie",this);
bonnie->show();
QPushButton *clyde = new QPushButton("Clyde",this);
clyde->show();
QPropertyAnimation *anim1 = new QPropertyAnimation(bonnie, "geometry",this);
// Set up anim1
anim1->setDuration(10000);
anim1->setStartValue(bonnie->rect());
anim1->setEndValue(bonnie->rect().adjusted(100,300,100,300));
QPropertyAnimation *anim2 = new QPropertyAnimation(clyde, "geometry",this);
// Set up anim2
anim2->setDuration(10000);
anim2->setStartValue(clyde->rect());
anim2->setEndValue(clyde->rect().adjusted(300,100,300,100));
QParallelAnimationGroup *group = new QParallelAnimationGroup(this);
group->addAnimation(anim1);
group->addAnimation(anim2);
group->start();
resize(400,400);
}
一个并行组可以同时播放多个动画。 调用其start()函数将启动其控制的所有动画。
毫无疑问,QSequentialAnimationGroup按顺序播放其动画。 前一个动画结束后,它将开始列表中的下一个动画。
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
QPushButton *bonnie = new QPushButton("Bonnie",this);
bonnie->show();
QPushButton *clyde = new QPushButton("Clyde",this);
clyde->show();
QPropertyAnimation *anim1 = new QPropertyAnimation(bonnie, "geometry",this);
// Set up anim1
anim1->setDuration(10000);
anim1->setStartValue(bonnie->rect());
anim1->setEndValue(bonnie->rect().adjusted(100,300,100,300));
QPropertyAnimation *anim2 = new QPropertyAnimation(clyde, "geometry",this);
// Set up anim2
anim2->setDuration(10000);
anim2->setStartValue(clyde->rect());
anim2->setEndValue(clyde->rect().adjusted(300,100,300,100));
QSequentialAnimationGroup *group = new QSequentialAnimationGroup(this);
group->addAnimation(anim1);
group->addAnimation(anim2);
group->start();
resize(400,400);
}
由于动画组本身就是动画,因此可以将其添加到另一个组中。 这样,您可以构建动画的树结构,该树结构指定动画何时相对于彼此播放。
八、动画和状态
使用状态机时,我们可以使用QSignalTransition或QEventTransition类将一个或多个动画与状态之间的过渡相关联。 这些类均从QAbstractTransition派生,QAbstractTransition定义了便捷功能addAnimation(),该功能可添加过渡发生时触发的一个或多个动画。
我们还可以将属性与状态相关联,而不必自己设置开始和结束值。 下面是一个完整的代码示例,该示例对QPushButton的几何图形进行动画处理。
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
QPushButton *button = new QPushButton("Animated Button",this);
button->show();
QStateMachine *machine = new QStateMachine(this);
QState *state1 = new QState(machine);
state1->assignProperty(button, "geometry", QRect(0, 0, 200, 30));
machine->setInitialState(state1);
QState *state2 = new QState(machine);
state2->assignProperty(button, "geometry", QRect(250, 250, 200, 30));
QSignalTransition *transition1 = state1->addTransition(button,&QPushButton::clicked, state2);
transition1->addAnimation(new QPropertyAnimation(button, "geometry"));
QSignalTransition *transition2 = state2->addTransition(button,&QPushButton::clicked, state1);
transition2->addAnimation(new QPropertyAnimation(button, "geometry"));
machine->start();
resize(500,300);
}
有关如何使用状态机框架进行动画制作的更全面的示例,请参见状态示例(它位于examples / animation / states目录中)。
九、总结
这篇可以应该是动画框架的大纲,应用动画框架很简单,随时都可以插入动画代码。另外本文还穿插了状态机框架的内容,可以两者对比,理解各自的优势是啥。
当前属性动画类才是用的比较多的动画类,只要看动画框架的文章就能懂得使用,也没必要去深究动画框架里的其他类,翻译其他的文档是为了动画框架的完整性。^ _ ^!