动画框架

动画框架旨在提供一种创建动画和平滑GUI的简便方法. 通过对Qt属性进行动画处理, 框架为动画widget和其他QObject提供了极大的自由度. 动画框架也可以与图形视图框架一起使用. 动画框架的许多概念也适用于Qt Quick, Qt Quick提供定义动画的声明性方式. 动画框架的许多知识适用于 Qt Quick.

本文解释动画框架体系结构的基础, 并通过示例, 展示动画框架对QObject和图形视图中的图元进行动画处理的最常见技术.

动画架构

在本节中, 我们从较高层次介绍动画框架的体系结构以及如何对Qt属性进行动画.下图显示动画框架中最重要的类.

动画框架基础由基类 QAbstractAnimation及其子类 QVariantAnimationQAnimationGroup组成. QAbstractAnimation 是所有动画类的祖先. 它定义框架中所有动画通用的基本属性及功能(如动画开始, 暂停, 停止). 它还接收时间变更通知.

动画框架进一步提供 QPropertyAnimation 类, 这个类继承自 QVariantAnimation 并执行Qt属性的动画, 它是 Qt 元对象系统的一部分. 这个类使用缓和曲线在属性上插值. 因此, 你想使一个值动画时, 你可以将它声明成一个属性并使你的类继承自 QObject. 对于已经存在的widget和 QObject 对象, 这种方式赋予我们极大的自由.

复杂的动画通过构建 QAbstractAnimation树形结构. 这个树形结构使用容器类 QAnimationGroup实现. QAnimationGroupQAbstractAnimation的子类, 因此QAnimationGroup之间可以相互包含.

动画框架既可以单独使用, 也可以作为状态机框架的一部分. 状态机提供一种特殊的状态用于播放动画. 状态进入或退出时, QState 也可以设置属性. 给定一个 QPropertyAnimation, 这个动画状态将插入到这些值之间. 稍后详细描述.

在框架底层, 动画由一个全局定时器控制, 这个定时器向所有正在播放的动画发送 更新.

关于类的详细信息查阅它的类文档.

所有类

下面这些类用于创建简单和复杂动画.

QAbstractAnimation

所有动画类的基类

QAnimationGroup

动画分组容器的抽象基类

QParallelAnimationGroup

平行动画分组

QPauseAnimation

暂停QSequentialAnimationGroup

QPropertyAnimation

动画属性

QSequentialAnimationGroup

序列动画分组

QVariantAnimation

动画基类

QEasingCurve

控制动画的缓和曲线

QTimeLine

控制动画的时间线

动画属性

如上节所述, 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子类 (QSequentialAnimationGroupQParallelAnimationGroup) 是动画类的容器, 因此这些动画可以按照顺序或并行处理. QAnimationGroup 是一个不对属性处理的动画类, 但是它接收定时器的时间更新通知, 然后将时间更新通知转发给容器中的动画类, 从而控制动画播放时间.

下列示例同时使用 QSequentialAnimationGroupQParallelAnimationGroup.


  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 按照顺序播放动画. 前一个动画结束后启动列表中的下一个动画.

由于动画容器类也继承自动画基类, 你能将其添加到另一个分组中. 这样, 你可以构建属性结构的动画类, 指定每个组以何种方式播放动画.

动画和状态

使用 状态机时, 我们可以使用QSignalTransitionQEventTransition在状态切换时关联一个或多个动画. QSignalTransitionQEventTransition继承自 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 ).