动画机制
Qt的动画框架提供了一种简单的方式创建GUI动画。通过Qt的属性系统机制,该框架可灵活的使窗口控件和QObject的子类创建动画。同时,该框架也同样适用于Qt的Graphics/View图形视图框架。
本文主要讲解动画框架的基础。通过使QObject的子类、图形单元项动画的基本例子,来演示如何使用。
动画架构
首先我们从高层上看下Qt动画的架构。如下图是动画框架的类图:
- QAbstractAnimation:所有动画对象的基类。它表示框架中所有动画都具有的基本属性,如启动、停止、暂停动画。
- QPropertyAnimation: 执行Qt属性系统的动画。我们可灵活的对窗口部件、QObject的子类创建属性动画。若想对自己的类成员变量创建动画,首先把它声明成属性Q_PROPERTY,然后类继承QObject。
- QAnimationGroup:管理多个复杂动画的容器。同时,动画组容器可以嵌套使用。
- QParallelAnimationGroup:管理动画的容器,多个动画并行运行。
- QSequentialAnimationGroup:管理动画的容器,多个动画串行运行。
在这背后,动画机制是由一个全局的定时器控制的,但运行时会更新所有的动画对象。
常用类
核心类 | 作用 |
---|---|
QAbstractAnimation | 所有动画的基类 |
QAnimationGroup | 管理动画的容器基类 |
QEasingCurve | 控制动画的过度曲线 |
QParallelAnimationGroup | 平行动画组容器 |
QSequentialAnimationGroup | 串行动画组容器 |
QPauseAnimation | 管理动画的容器基类 |
QPropertyAnimation | 控制动画的过度曲线 |
QTimeLine | 控制动画的时间线 |
Qt属性动画
主要通过QPropertyAnimation完成属性动画,内部会自动插值计算。比如QWidget有边界、位置、大小等属性,可对这些属性简单动画。举个例子,将一个按钮在10s内,从位置(0,0)移动到(250,250):
QPushButton button("Animated Button");
button.show();
QPropertyAnimation animation(&button, "geometry"); //指定要创建动画的目标对象、及属性
animation.setDuration(10000); //设置动画持续的时间
animation.setStartValue(QRect(0, 0, 100, 30)); //设置属性geometry的起始大小
animation.setEndValue(QRect(250, 250, 100, 30)); //设置属性geometry的起始大小
animation.start();
动画框架内部会根据起点和终点自动线性插值计算,在中间计算出合适的值,然后根据这些算出的若干点运行动画。
再看一个例子,自由的定义若干关键点。将一个按钮在8s内,从位置(0,0)移动到(250,250),然后在2s内,再将位置从(250,250)移动到(0,0):
QPushButton button("Animated Button");
button.show();
QPropertyAnimation animation(&button, "geometry");
animation.setDuration(10000);
animation.setKeyValueAt(0, QRect(0, 0, 100, 30)); //设置关键点10 * 0s时的属性值
animation.setKeyValueAt(0.8, QRect(250, 250, 100, 30)); //设置关键点10 * 0.8s时的属性值
animation.setKeyValueAt(1, QRect(0, 0, 100, 30)); //设置关键点10 * 1s时的属性值
animation.start();
更灵活的是,可对自己申明的类创建属性动画。只需要两步:类继承于QObject;声明属性Q_PROPERTY。例子如下:
class MyGraphicsRectItem : public QObject, public QGraphicsRectItem //继承QObject
{
Q_OBJECT
Q_PROPERTY(QRectF geometry READ geometry WRITE setGeometry) //声明属性Q_PROPERTY
};
记住一点:自定义属性需要提供get/set方法。
动画框架与图形视图框架
如果想对图形项QGraphicsItem创建动画,我们可以使用属性动画QPropertyAnimation。但是QGraphicsItem不是继承于QObject,因此就不能直接使用对QGraphicsItem使用属性动画。一种解决方式:对QGraphicsItem创建子类,子类同时继承于QObject、QGraphicsItem。代码如下:
class Pixmap : public QObject, public QGraphicsPixmapItem
{
Q_OBJECT
Q_PROPERTY(QPointF pos READ pos WRITE setPos)
...
需要注意:由于Qt元对象机制,QObject必须是第一个被继承的类。
过度曲线
使用QPropertyAnimation属性动画时,可定义多个关键点。此外也可使用内置的过度曲线QEasingCurve(实际上就是如何对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::Type内置类型很多,具体效果请查看Qt帮助文档。此外,也可以定制过度类型,然后注册到QEasingCurve中。
组合动画
实际中要运行的动画不止一个,好在Qt动画组可以管理并运行多个动画,如下所示:
1. QAnimationGroup:管理多个复杂动画的容器。同时,动画组容器可以嵌套使用。
2. QParallelAnimationGroup:管理动画的容器,多个动画并行运行。
3. QSequentialAnimationGroup:管理动画的容器,多个动画串行运行。
举个并行动画的例子:
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); //添加属性动画1
group->addAnimation(anim2); //添加属性动画2
group->start(); //开始执行属性动画组:属性动画1、2并行执行
举个并行串行的例子:
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); //添加属性动画1
group.addAnimation(&anim2); //添加属性动画2
group.start(); //开始执行属性动画组:属性动画1、2串行执行。先执行动画1、在执行动画2。
因QAnimationGroup可嵌套,因此我们能组装成更复杂的动画组。
动画和状态机制
这里先看个简单代码例子,后期在梳理Qt状态机机制时再详细说明。
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();
至此,本章主要讲解了Qt框架的使用场景、简单机制及核心类使用。下章结合一个具体的案例练练手。