动画框架
动画框架旨在提供一种创建动画和平滑GUI的简便方法. 通过对Qt属性进行动画处理, 框架为动画widget和其他QObject提供了极大的自由度. 动画框架也可以与图形视图框架一起使用. 动画框架的许多概念也适用于Qt Quick, Qt Quick提供定义动画的声明性方式. 动画框架的许多知识适用于 Qt Quick.
本文解释动画框架体系结构的基础, 并通过示例, 展示动画框架对QObject和图形视图中的图元进行动画处理的最常见技术.
动画架构
在本节中, 我们从较高层次介绍动画框架的体系结构以及如何对Qt属性进行动画.下图显示动画框架中最重要的类.
动画框架基础由基类 QAbstractAnimation及其子类 QVariantAnimation 和 QAnimationGroup组成. QAbstractAnimation 是所有动画类的祖先. 它定义框架中所有动画通用的基本属性及功能(如动画开始, 暂停, 停止). 它还接收时间变更通知.
动画框架进一步提供 QPropertyAnimation 类, 这个类继承自 QVariantAnimation 并执行Qt属性的动画, 它是 Qt 元对象系统的一部分. 这个类使用缓和曲线在属性上插值. 因此, 你想使一个值动画时, 你可以将它声明成一个属性并使你的类继承自 QObject. 对于已经存在的widget和 QObject 对象, 这种方式赋予我们极大的自由.
复杂的动画通过构建 QAbstractAnimation树形结构. 这个树形结构使用容器类 QAnimationGroup实现. QAnimationGroup 是 QAbstractAnimation的子类, 因此QAnimationGroup之间可以相互包含.
动画框架既可以单独使用, 也可以作为状态机框架的一部分. 状态机提供一种特殊的状态用于播放动画. 状态进入或退出时, QState 也可以设置属性. 给定一个 QPropertyAnimation, 这个动画状态将插入到这些值之间. 稍后详细描述.
在框架底层, 动画由一个全局定时器控制, 这个定时器向所有正在播放的动画发送 更新.
关于类的详细信息查阅它的类文档.
所有类
下面这些类用于创建简单和复杂动画.
所有动画类的基类 | |
动画分组容器的抽象基类 | |
平行动画分组 | |
暂停QSequentialAnimationGroup | |
动画属性 | |
序列动画分组 | |
动画基类 | |
控制动画的缓和曲线 | |
控制动画的时间线 |
动画属性
如上节所述, QPropertyAnimation 对Qt属性插值. 这个类经常用于值动画; 实际上, QPropertyAnimation的基类 QVariantAnimation有一个空函数updateCurrentValue(), 我们可以接收 valueChanged 信号, 改变属性值; 否则, 属性值不变.
我们选择动画Qt属性的一个很重要的原因是: 它能很自由地动画Qt API现有类. 例如, QWidget 类 具有边框, 颜色等属性. 让我们看一个小的示例:
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秒(10000毫秒)内将 按钮
从左上角移动到坐标点 (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秒内移动到初始位置. 移动的点采用线性插值.
你也可以对未声明为Qt属性的 QObject 的值进行动画处理. 唯一的要求是这个值有一个 setter. 你可以创建包含该值的类的子类, 并使用这个setter声明一个属性. 注意每个Qt属性都要求一个getter, 因此你需要提供自己的getter定义.
class MyGraphicsRectItem : public QObject, public QGraphicsRectItem { Q_OBJECT Q_PROPERTY(QRectF geometry READ geometry WRITE setGeometry) };
上述代码中, 我们子类化 QGraphicsRectItem 并定义一个几何属性. 现在,即使 QGraphicsRectItem 没有提供几何属性定义, 我们可以动画几何属性.
有关Qt系统属性的介绍, 参见 属性概述.
动画及图形视图框架
想为QGraphicsItem设置动画, 你也可以使用 QPropertyAnimation . 然而, QGraphicsItem 不是继承自 QObject. 一个好的方案是为需要的图元类自定义子类. 这个类同时继承自 QObject. 这样, QGraphicsItem才可以使用QPropertyAnimation . 下面示例展示如何实现上述方案. 另一个方案是子类化 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.
将动画放在一起
一个应用程序经常包含多个动画. 例如, 你可能同时移动多个图元, 或依次移动.
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 按照顺序播放动画. 前一个动画结束后启动列表中的下一个动画.
由于动画容器类也继承自动画基类, 你能将其添加到另一个分组中. 这样, 你可以构建属性结构的动画类, 指定每个组以何种方式播放动画.
动画和状态
使用 状态机时, 我们可以使用QSignalTransition 或 QEventTransition在状态切换时关联一个或多个动画. 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
).