The Animation Framework
动画框架目标在于提供一种简便的方式来创建动态的,流畅的GUI.对于动画部件和其他的QObject框架通过动画属性提供了很不错的自由度.动画框架也可以用于图形视图框架.Qt Quick通过一种声明式定义动画的方法使许多应用在动画框架的概念同样适用.通过动画框架所知的大部分都能应用在Qt Quick.
以下概述中解释了架构的基础知识.也展示了框架允许的对QObject和图形项动画化最常用技术的示例.
动画架构
这部分将高度概括动画框架的架构和如何使用动画属性.下图展示了动画框架中最重要的一些类.
动画框架由基类QAbstractAnimation,和它的两个字类QVariantAnimation和QAnimationGroup组成. QAbstractAnimation是所有动画类的祖先.它代表了框架内所有动画类共有的基础属性;特别是它能开始,停止,暂停动画,也能接收时间改变的通知.
动画框架进一步提供了QPropertyAnimation类,它继承自QVariantAnimation和执行动画的Qt属性,是Qt的meta-object system(元对象系统)的一部分.QPropertyAnimation使用缓动曲线对属性插值.所以想要动画一个值时可以将它声明为一个属性,并使拥有该属性的类成为QObject类.这样使在动画一个已经存在的部件或其他QObject变得非常方便.
可以通过构造树形结构的QAbstractAnimation构造复杂动画.这个树形结构由QAnimationGroup组建,在结构中QAnimationGroup作为其他动画的容器.特别地,QAnimationGroup是QAbstractAnimation的子类,所以QAnimationGroup可以包含QAnimationGroup自身.
动画框架可以单独使用,但同时它也被设计为状态机框架的一部分(在state machine framework页对状态机进行了介绍).状态机提供一个可以播放动画的特别状态.QState在进入或退出时也可以设置属性,并且这个特殊的动画状态会插入到给定的一组QPropertyAnimation.稍后会更加详细了解.
在幕后,动画由全局定时器向正在播放的动画发送updates来控制.
框架内类更详细的的功能和用法可以在它们类的描述中找到.
框架中的类
这些类提供了创造简单和复杂动画的框架.
所有动画类的基础 | |
组动画的抽象基类 | |
控制动画的缓动曲线 | |
平行动画组 | |
暂停QSequentialAnimationGroup | |
动画的Qt属性 | |
连续动画组 | |
动画控制时间线 | |
动画的基类 |
动画的Qt属性
正如上部分所提及的QPropertyAnimation类可以插入Qt属性.这个类通常应该用于值的动画;实际上,它的超类QVariantAnimation,空实现了updateCurrentValue(),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();
以上代码将在10s(10000ms)内移动button
从屏幕左上角位置到位置(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();
在这个示例中,动画将在8s内移动button到位置(250, 250),然后再剩余的2s返回原来的位置. 运动将在这些点内,线性插值.
也可以为没有声明为QObject的属性的值动画化.只要对象有这个值的setter.然后可以在包含这个值的类子类化时,使用setter声明为属性.需要注意的是每个Qt属性需要有getter,属性没有定义getter时需要自己提供.
class MyGraphicsRectItem : public QObject, public QGraphicsRectItem { Q_OBJECT Q_PROPERTY(QRectF geometry READ geometry WRITE setGeometry) };
以上示例代码中,子类化了QGraphicsRectItem,并定义了geometry属性.之后就可以动画化部件的geometry,即使QGraphicsRectItem没有提供geometry属性.
关于Qt属性系统的一般介绍,参看overview.
动画和图形视图框架
当想动画化QGraphicsItem时,也可以使用QPropertyAnimation.然而,QGraphicsItem并没有继承QObject.好的办法是将想要动画化的图形项子类化,这个类同时也继承QObject.这样,QPropertyAnimation就可以被用于QGraphicsItem.以下示例展示了如何这样做.QGraphicsItem也可以继承QGraphicsWidget, QGraphicsWidget是一个QObject类.
class Pixmap : public QObject, public QGraphicsPixmapItem { Q_OBJECT Q_PROPERTY(QPointF pos READ pos WRITE setPos) ...
正如上一节所讲述的,需要为想要动画化的部件定义属性.
特别地,元对象系统要求QObject必须被首先继承.
缓动曲线
如前所述,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.
动画组
应用程序常常包含有不止一个动画.比如,相似地移动多个图形项,或者依次移动移动图形项.
The subclasses of QAnimationGroup (QSequentialAnimationGroup和QParallelAnimationGroup)的子类是其他动画的容器,使得这些动画可以有序或平行地动画.QAnimationGroup像是一个没有动画属性的动画,但它会定期收到时间改变的通知.这使得它能够发送时间变化给所包含的动画,从而控制被包含动画的播放.
以下示例使用了QSequentialAnimationGroup和QParallelAnimationGroup,首先是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 依次播放动画的示例.它在组内的前一个动画播放完毕后再播放下一个.
因为动画组本身就是动画,所以也可以被添加到其他动画组.这样就可以将指定了动画播放和关系的动画构建成一组树形结构的动画.
动画与状态机
当使用state machine(状态机)时,可以使用QSignalTransition或QEventTransition类将一个或多个动画关联到状态之间的转换.这两个类都由QAbstractTransition派生,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();
关于如何在状态机框架使用动画的更全面的示例,请参看状态机示例(在 examples/animation/states
目录下).