## 简介

Qt动画框架旨在为创建动画和平滑的GUI提供了一种简单的方法。通过Qt动画属性，该框架为部件和其它QObject对象的动画操作提供了非常大的自由性，框架也可以被用于图形视图框架中。

本篇，我们阐述了Qt动画框架的基本结构。同时，会展示最常见的技术示例，用于动画操作QObject和图形项。

![这里写图片描述](assets/class1.png)

动画框架基础由基类QAbstractAnimation以及它的两个子类QVariantAnimation、QAnimationGroup组成。QAbstractAnimation是所有动画的祖先。它包含了一些在框架中被普遍使用的基本功能，尤其是启动、停止和暂停动画功能，它也接收定时触发通知。

Qt动画框架更是提供了QPropertyAnimation类，该类继承自QVariantAnimation，用于对Qt属性的动画操作(Qt属性系统是Qt元对象系统的一部分)。QPropertyAnimation类使用缓和曲线算法对属性进行插值演化操作。因此当你想使用动画改变一个值时，需要声明其为一个属性并且使该类继承自QObject。这给我们提供了很大的方便性，去动画操作现有的部件和其它的QObject对象。

复杂动画可以通过构建QAbstractAnimation树形结构来构造。该树主要使用QAnimationGroup,QAnimationGroup类是一个包含其它动画类的容器类;同时QAnimationGroup类也是QAbstractAnimation类的子类，因此一个容器可以包含其它容器。

Qt动画框架可以独立使用，但是也被设计为Qt状态机框架的一部分。状态机框架提供一个特殊的状态用来播放动画。当状态进入或者退出时，QState也可以改变属性。当这个动画状态提供了一个QPropertyAnimatio时，这个特殊的状态会在这些值之间进行篡改操作。后续我们将了解的更加仔细。

## 动画框架类

这些类提供了用于创建简单的和复杂的动画框架

| 类名                      | 描述                          |
| ------------------------- | ----------------------------- |
| QAbstractAnimation        | 所有动画类的基类              |
| QAnimationGroup           | 动画容器类的抽象类            |
| QEasingCurve              | 控制动画的缓和曲线类          |
| QParallelAnimationGroup   | 并行动画容器                  |
| QPauseAnimation           | QSequentialAnimationGroup暂停 |
| QPropertyAnimation        | Qt属性动画                    |
| QSequentialAnimationGroup | 串行动画容器                  |
| QTimeLine                 | 可控制动画的时间轴类          |
| QVariantAnimation         | 动画类的抽象基类              |

## QPropertyAnimation

#### 简介

QPropertyAnimation类定义了Qt的属性动画。

QPropertyAnimation以Qt属性做插值，作为属性值存储在QVariants中，该类继承自QVariantAnimation，所以基类支持的元素类型该类同样支持。

声明属性的类必须是一个QObject，为了能够让属性可以用做动画效果，必须提供一个setter(这样，QPropertyAnimation才可以设置属性的值)。注意:这能够使它让许多Qt控件产生动画效果。

**Example**

```cpp
QPushButton *btn = new QPushButton("Animation",this);

QPropertyAnimation* animation = new QPropertyAnimation(btn,"pos");
animation->setStartValue(QPoint(0,0));
animation->setEndValue(QPoint(520,400));
animation->setDuration(3000);
animation->start();
```

属性名和应用改动画属性的QObject实例被传递给构造函数。 然后可以指定属性的开始值和结束值。  

QVariantAnimation类描述详细说明了如何设置动画。 但是请注意，如果没有设置启动值，该属性将从创建QPropertyAnimation实例时的值开始。  

不同类的属性可以通过帮助文档查看，如果属性声明为const 那么将不能用于动画设置，而且用于动画的属性，一般都是能够直接观察到的，比如：大小，坐标，颜色...

<img src="assets/image-20210925030917789.png" alt="image-20210925030917789" style="zoom:67%;" />

#### 缓和曲线

在前面程序的运行效果中可以看到，按钮部件的运动过程都是线性的，即匀速运动。除了在动画中添加更多的关键点;还可以使用缓和曲线，缓和曲线描述了怎样来控制0和1之间的插值速度的功能，这样就可以在不改变插值的情况下来控制动画的速度。

```cpp
animation.setEasingCurve(QEasingCurve:OutBounce)
```

这里使用了QEasingCurve: : OutBounce缓和曲线，此时运行程序会发现，它会使按钮部件就像从开始位置掉落到结束位置的皮球一样出现弹跳效果。QEasingCurve类中提供了四十多种缓和曲线，而且还可以自定义缓和曲线，详细可以查看一下该类的帮助文档。Qt中还提供了一个Easing Curves Example的示例程序，可以演示所有缓和曲线的效果。



## QSequentialAnimationGroup

#### 简介

QSequentialAnimationGroup类提供了动画的串行组

QSequentialAnimationGroup是一个QAnimationGroup，它按顺序运行它的动画，也就是说，它在一个动画结束播放后开始另一个动画。 动画按照它们被添加到组的顺序播放(使用addAnimation()或insertAnimation())。 动画组在最后一个动画完成时结束。  

在每一个时刻，最多有一个动画是活跃的群体; 它由currentAnimation()返回。 空组没有当前的动画。  

串行动画组可以被视为任何其他动画，也就是说，它可以启动、停止，并添加到其他组中。 还可以调用addPause()或insertPause()向连续动画组添加暂停。  

​                                                                          

#### 公有函数

+ QPauseAnimation *addPause(int msecs)

  给这个动画组添加一个msecs的暂停。 暂停被认为是一种特殊类型的动画，因此animationCount将增加1。  

+ void clear()

  清除动画组所有动画

#### Eaxmple

下面，我们通过QSequentialAnimationGroup来构建一个串行动画组，并添加属性动画QPropertyAnimation，这里也可以使用addAnimation()添加其它动画/动画组，就不予演示了。

```cpp
void MainWindow::create()
{
    moreBtn = new QPushButton(this);
    addBtn = new QPushButton(this);
    playHistoryBtn = new QPushButton(this);
    skinBtn = new QPushButton(this);

    moreBtn->setFixedSize(32,32);
    addBtn->setFixedSize(32,32);
    playHistoryBtn->setFixedSize(32,32);
    skinBtn->setFixedSize(32,32);

    //设置坐标
    moreBtn->move(width()-32,0);
    addBtn->move(moreBtn->x()-32,-32);
    playHistoryBtn->move(addBtn->x()-32,-32);
    skinBtn->move(playHistoryBtn->x()-32,-32);


    auto *animation1 = new QPropertyAnimation(addBtn,"pos",this);
    animation1->setStartValue(QPoint(addBtn->x(),-32));
    animation1->setEndValue(QPoint(addBtn->x(),moreBtn->y()));
    animation1->setDuration(1000);
    animation1->setEasingCurve(QEasingCurve::OutBounce);


    auto *animation2 = new QPropertyAnimation(playHistoryBtn,"pos",this);
    animation2->setStartValue(QPoint(playHistoryBtn->x(),-32));
    animation2->setEndValue(QPoint(playHistoryBtn->x(),moreBtn->y()));
    animation2->setDuration(1000);
    animation2->setEasingCurve(QEasingCurve::OutBounce);

    auto *animation3 = new QPropertyAnimation(skinBtn,"pos",this);
    animation3->setStartValue(QPoint(skinBtn->x(),-32));
    animation3->setEndValue(QPoint(skinBtn->x(),moreBtn->y()));
    animation3->setDuration(1000);
    animation3->setEasingCurve(QEasingCurve::OutBounce);


    auto group = new QSequentialAnimationGroup(this);
    group->addAnimation(animation1);
    group->addAnimation(animation2);
    group->addAnimation(animation3);


    connect(moreBtn,&QPushButton::clicked,this,[=]
    {
        if(skinBtn->y()>=0)
        {
            group->setDirection(QSequentialAnimationGroup::Backward);
        }
        else
        {
            group->setDirection(QSequentialAnimationGroup::Forward);
        }
        group->start();
    });
}
```

## QParallelAnimationGroup

QParallelAnimationGroup类提供动画的并行组。

QParalleAnimationGroup -一个动画容器，当它启动的时候它里面的所有动画也启动，即:并行运行所有动画，当持续时间最长的动画完成时动画组也随之完成。

```cpp
QParallelAnimationGroup *group = new QParallelAnimationGroup;

group->addAnimation(animation1);
group->addAnimation(animation2);
group->addAnimation(animation3);
```

## QPauseAnimation

QPauseAnimation类为QSequentialAnimationGroup提供了一个暂停。

如果你想为QSequentialAnimationGroup动画之间添加延迟，可以插入一个QPauseAnimation。 它没有任何动画，但当在指定的毫秒数之内开始运行时不会结束。可以通过构造函数指定暂停的时间，也可以通过setDuration()设置。

没必要自己建立一个QPauseAnimation, QSequentialAnimationGroup提供 了便利的函数addPause()和insertPause(),这些函数可以简单地暂停应该持续的毫秒数。

```cpp
group->addAnimation(oneAnimation);
//直接添加
group->addPause(1000);
group->addAnimation(twoAnimation);
group->addAnimation(threeAnimation);

//创建对象再添加
QPauseAnimation* pause = new QPauseAnimation(1000);

group->addAnimation(oneAnimation);
group->addAnimation(pause);			//<-看这
group->addAnimation(twoAnimation);
group->addAnimation(threeAnimation);
```

## QTimeLine

QTimeLine类提供了用于控制动画的时间轴，通常用于通过定期调用一个槽函数来动画一个GUI控件。

相信了解动画的人对帧应该不陌生，可以把一个动画想象成由很多张静态画面组成，而每一个画面就是一帧图像。每隔一定时间间隔就显示—帧图像，当该间隔较短时，人眼就感觉不出来了，觉得看到的是连续的影像。

### 基本使用

可以通过将持续时间（毫秒)传递给QTimeLine的构造函数来构建timeline，timeline的持续时间描述动画将运行多长时间。然后通过调用setFrameRange()设置合适的帧范围。最后，将frameChanged()信号连接到想要动画的部件中的合适的槽函数（例如，QProgressBar 中的setValue()) 。

调用start()，QTimelL ine将进入运行状态，并开始定期（固定的时间间隔)发出frameChanged()信号，部件的连接属性的值以稳定的速度从帧范围的下限到上限增长。可以通过调用setJpdateIlnterval()指定更新间隔。当完成后，QTimeLine进入NotRunning 状态，并发出finished()信号。

```cpp
QTimeLine * timeLine = new QTimeLine(2000);
timeLine->setFrameRange(0,100);
//timeLine->setEasingCurve(QEasingCurve::OutBounce);				//设置缓和曲线
timeLine->setCurveShape(QTimeLine::CurveShape::EaseInOutCurve);		//设置曲线形状
    
connect(timeLine,&QTimeLine::frameChanged,ui->progressBar,&QProgressBar::setValue);	//帧改变
connect(timeLine,&QTimeLine::valueChanged,this,[=](qreal value)						//值改变(比例)
{
     qDebug()<<value;
});
timeLine->setLoopCount(0);	//0无限循环  默认是1
timeLine->start();
```





## 窗口动画（下坠、抖动、透明度）

前面我们学习了动画的基本使用，现在我们用起来，实现一些特效~

下面，我们以geometry、pos、windowOpacity属性为例，来实现窗体的下坠、抖动、透明度效果。

### 下坠效果

```cpp
void Widget::onDropWindow()
{
    auto* animation = new QPropertyAnimation(this, "geometry");
    animation->setStartValue(QRect(x(), 0, width(), height()));
    animation->setEndValue(this->geometry());
    animation->setEasingCurve(QEasingCurve::OutElastic);
    animation->setDuration(2000);
    animation->start(QPropertyAnimation::DeletionPolicy::DeleteWhenStopped);
}
```

<img src="assets/drop.gif" style="zoom:67%;" />

### 抖动效果

```cpp
void Widget::onShakeWindow()
{
    auto* animation = new QPropertyAnimation(this, "pos");
    animation->setKeyValueAt(0, pos() - QPoint(3, 0));
    animation->setKeyValueAt(0.25, pos() - QPoint(0, 3));
    animation->setKeyValueAt(0.5, pos() - QPoint(0, -3));
    animation->setKeyValueAt(0.75, pos() - QPoint(-3, 0));
    animation->setKeyValueAt(0, pos());
    animation->setLoopCount(3); //重复三次
    animation->setDuration(100);
    animation->start(QPropertyAnimation::DeletionPolicy::DeleteWhenStopped);
}
```

<img src="assets/shake.gif" style="zoom:67%;" />

### 透明

```cpp
void Widget::onOpacityWindow()
{
    auto* animation = new QPropertyAnimation(this, "windowOpacity");
    animation->setStartValue(1);
    animation->setKeyValueAt(0.25,0.75);
    animation->setKeyValueAt(0.5,0.5);
    animation->setKeyValueAt(0.75,0.25);
    animation->setEndValue(1);
    animation->setDuration(1000);
    animation->start();
}
```

<img src="assets/opacity.gif" style="zoom:67%;" />



## 状态机

### QState

QState类为QStateMachine提供了一个通用状态。  

QState对象可以有子状态，也可以转换到其他状态。 QState是状态机框架的一部分。  

addTransition()函数的作用是添加一个转换。 

removeTransition()函数的作用是删除转换。 

transitions()函数的作用是返回状态的传出转换。  

assignProperty()函数用于定义在进入状态时应执行的属性赋值。  

顶级状态必须传递一个QStateMachine对象作为它们的父状态，或者使用QStateMachine::addState()添加到状态机。  



### QStateMachine

QStateMachine类提供了一个分层的有限状态机  

```cpp
    auto playBtn = new QPushButton("播放",this);
    //设置 状态
    QState *playState = new QState;
    playState->assignProperty(playBtn,"text","播放");

    QState *pauseState = new QState;
    pauseState->assignProperty(playBtn,"text","暂停");

    QState *stopState = new QState;
    stopState->assignProperty(playBtn,"text","停止");

    //给状态添加转换 点击播放按钮切换到暂停状态
    playState->addTransition(playBtn,&QPushButton::clicked,pauseState);
    pauseState->addTransition(playBtn,&QPushButton::clicked,stopState);
    stopState->addTransition(playBtn,&QPushButton::clicked,playState);


    //创建状态机并添加状态
    QStateMachine *stateMachine = new QStateMachine(this);
    stateMachine->addState(playState);
    stateMachine->addState(pauseState);
    stateMachine->addState(stopState);

    //设置初始状态
    stateMachine->setInitialState(playState);

    //启动状态机
    stateMachine->start();
```

