当前位置:   article > 正文

Qt动画技术汇总

qt动画

一、Qt动画框架

在这里插入图片描述

Qt的动画框架由基类QAbstractAnimation以及它的两个子类QVariantAnimation、QAnimationGroup组成。基础动画由QVariantAnimation的子类QPropertyAnimation来设置,再通过将多个QPropertyAnimation和QPauseAnimation组合成为动画组(QParallelAnimationGroup、QSequentialAnimationGroup),完成一个连续的动画。

1.1 QPropertyAnimation

QPropertyAnimation类能够修改Qt的属性值,如pos、geometry等属性。设置好动画的初值和末值,以及持续的时间后,一个属性动画就基本完成了。

1.1.1 缩放

通过修改控件的geometry属性可以实现缩放效果,也可以实现位移的动画,该属性的前两个值确定了控件左上角的位置,后两个值确定了控件的大小。
示例代码:

QPropertyAnimation *pScaleAnimation1 = new QPropertyAnimation(ui-。scaleButton, "geometry");
pScaleAnimation1->setDuration(1000);
pScaleAnimation1->setStartValue(QRect(190, 230, 0, 0));
pScaleAnimation1->setEndValue(QRect(120, 160, 140, 140));
pScaleAnimation1->start();
  • 1
  • 2
  • 3
  • 4
  • 5

示例效果:
在这里插入图片描述

1.1.2 位移

如果只是需要位移动画的话,修改控件的pos属性即可。pos属性就是控件的左上角所在的位置。
示例代码:

QPropertyAnimation *pPosAnimation1 = new QPropertyAnimation(ui.posButton, "pos");
pPosAnimation1->setDuration(1000);
pPosAnimation1->setStartValue(QPoint(360, 160));
pPosAnimation1->setEndValue(QPoint(360, 350));
pPosAnimation1->setEasingCurve(QEasingCurve::InOutQuad);
pPosAnimation1->start();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

示例效果:
在这里插入图片描述

1.1.3 不透明度

Qt的控件没有单独的透明度属性,要修改控件的透明度可以通过QGraphicsOpacityEffect类来实现。
示例代码:

QGraphicsOpacityEffect *pButtonOpacity = new QGraphicsOpacityEffect(this);
pButtonOpacity->setOpacity(1);
ui.opacityButton->setGraphicsEffect(pButtonOpacity);

QPropertyAnimation *pOpacityAnimation1 = new QPropertyAnimation(pButtonOpacity, "opacity");
pOpacityAnimation1->setDuration(1000);
pOpacityAnimation1->setStartValue(1);
pOpacityAnimation1->setEndValue(0);
pOpacityAnimation1->start();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

示例效果:
在这里插入图片描述

1.1.4 松弛曲线

动画还可以设置时间的插值曲线,默认是linear,即线性运动,通过设置QEasingCurve即可。Qt提供了40种已经定义好的曲线(如果有需要也可以自定义曲线):
示例代码:

pScaleAnimation1->setEasingCurve(QEasingCurve::InOutQuad);
  • 1

示例效果:
在这里插入图片描述

1.2 QSequentialAnimationGroup

1.2.1 串行动画分组

通过将QPropertyAnimation或者QPauseAnimation加入,构成一个按加入顺序依次播放的动画组,动画组的总时长是各个加入动画的总和。
示例代码:

QSequentialAnimationGroup *pPosGroup = new QSequentialAnimationGroup(this);
pPosGroup->addPause(500);
pPosGroup->addAnimation(pScaleAnimation1);
pPosGroup->addPause(500);
pPosGroup->addAnimation(pPosAnimation1);
pPosGroup->addPause(500);
pPosGroup->addAnimation(pOpacityAnimation1);
pPosGroup->start();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

示例效果:
在这里插入图片描述

1.2.2 往返运动

Qt的动画可以设置循环次数,默认的循环是从头再播放一遍,往返运动可以在一个串行动画组中加入初值末值相反的一组动画来实现。

1.3 QParallelAnimationGroup

1.3.1 并行动画组

加入并行动画组的动画会同时播放,动画组的总时长是最长的动画所需的时间。
示例代码:

QParallelAnimationGroup *pParallGroup = new QParallelAnimationGroup(this);
pParallGroup->addAnimation(pScaleAnimation1);
pParallGroup->addAnimation(pPosAnimation1);
pParallGroup->addAnimation(pOpacityAnimation1);
pParallGroup->start();
  • 1
  • 2
  • 3
  • 4
  • 5

示例效果:
在这里插入图片描述

1.3.2 延时播放

在串行动画组的开始先加入一个QPauseAnimation,再将Pause不同的串行动画组加入并行动画组就可以实现延时效果了。

1.3.3 动画方向

默认动画是从开始到结束这个方向播放的, 可以设置为从结束到开始播放。
示例代码:

m_group->setDirection(QAbstractAnimation::Backward);
  • 1

1.4 QTimeLine

QTimeLine提供了用于控制动画的时间线,可以通过信号槽的方式使用。
示例代码:

ui.progressBar->setValue(0);
ui.progressBar->setRange(0, 100);

/* Construct a 1-second timeline with a frame range of 0 - 100 */
QTimeLine *timeLine = new QTimeLine(1000, this);
timeLine->setFrameRange(0, 100);
connect(timeLine, SIGNAL(frameChanged(int)), ui.progressBar, SLOT(setValue(int)));

/* Clicking the push button will start the progress bar animation */
connect(ui.pushButton, SIGNAL(clicked()), timeLine, SLOT(start()));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

示例效果:
在这里插入图片描述

二、动画和状态

当使用状态机时候,我们可以将彼此状态之间用一个或者多个动画关联起来。我们也可以设置各个不同的状态的属性,而不用设置动画的startValues和endValue。这里仅简要介绍Qt状态机,详细可以参考Qt 助手《The State Machine Framework | Qt Core 5.6》对Qt状态机的介绍。

2.1 Qt状态机简介

在这里插入图片描述

Qt的状态机框架主要包括三部分:

  • 以QAbstractState为基类的QState,以及QFinalState,QHistoryState等表示状态的类
  • 以QAbstractTransition为基类的用来表示各类状态转换行为(Transition)的类(包括:事件触发/信号触发/鼠标键盘触发的转换等类别)
  • QStateMachine状态机类

下面示意图简单演示了QStateMachine的API调用:

示例代码:

/* The following snippet shows the code needed to create such a state machine. First, we create the state machine and states:*/

      QStateMachine machine;
      QState *s1 = new QState();
      QState *s2 = new QState();
      QState *s3 = new QState();

/* Then, we create the transitions by using the QState::addTransition() function: */

      s1->addTransition(button, SIGNAL(clicked()), s2);
      s2->addTransition(button, SIGNAL(clicked()), s3);
      s3->addTransition(button, SIGNAL(clicked()), s1);

/* Next, we add the states to the machine and set the machine's initial state: */

      machine.addState(s1);
      machine.addState(s2);
      machine.addState(s3);
      machine.setInitialState(s1);

/* Finally, we start the state machine: */

      machine.start();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

示例效果:
在这里插入图片描述

2.2 简单示例

使用QSignalTransition或者QEventTransition类,这两个类都继承自QAbstractTransition,QAbstractTransition提供了方便的函数addAnimation(),可以再状态转换触发时候播放一个或者多个动画animations。下面是一个关联按钮QPushButton位置属性的动画示例:

示例效果:
在这里插入图片描述

示例代码:

QStateMachine *machine = new QStateMachine;

QState *state1 = new QState(machine);
state1->assignProperty(ui.pushButton, "geometry", QRect(30, 250, 100, 30));
machine->setInitialState(state1);

QState *state2 = new QState(machine);
state2->assignProperty(ui.pushButton, "geometry", QRect(250, 250, 100, 30));

QSignalTransition *transition1 = state1->addTransition(ui.pushButton, SIGNAL(clicked()), state2);
transition1->addAnimation(new QPropertyAnimation(ui.pushButton, "geometry"));

QSignalTransition *transition2 = state2->addTransition(ui.pushButton, SIGNAL(clicked()), state1);
transition2->addAnimation(new QPropertyAnimation(ui.pushButton, "geometry"));

machine->start();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

三、实例演示

AnimatedCheckBox: 带动画效果的CheckBox

3.1 实现效果

在这里插入图片描述

3.2 实现分析

  • Qt 提供了 QCheckBox,但是无法用 QSS 实现上述样式,而且也没有 indicator 移动的动画效果
  • 继承QCheckBox,有以下好处:(1)可以在使用QCheckBox的地方直接替换为AnimatedCheckBox;(2)不需要维护check box的各种状态;(3)相关的信号槽可以直接使用QCheckBox的
  • 继承QCheckBox,有以下问题:(1)QCheckBox虽然有一个indicator的subcontrol,但是满足不了需求,考虑使用 QLabel 来代替默认的 indicator;(2)默认点击 QCheckBox 的 indicator 或者文字时才能切换选中状态,点击 QCheckBox 上其他空白的地方没有反应,为了实现点击任何地方都能够切换选中状态,需要重写 mousePressEvent;(3)为了不使用 QCheckBox 的默认样式,需要实现一个空的 paintEvent
  • 使用布局管理器无法实现移动indicator,考虑使用绝对坐标定位的方式来控制indicator
  • 需要支持用.qss文件设置样式,不需要重新编译程序,因此不能用QPainter绘制
  • 需要支持控件大小不固定的情况,因此需要重写resizeEvent,根据AnimatedCheckBox的大小和是否checked来计算indicator的大小和位置
  • 使用 QPropertyAnimation 实现动画效果

3.3 控件骨架

AnimatedCheckBox.h

#ifndef ANIMATEDCHECKBOX_H
#define ANIMATEDCHECKBOX_H

#include <QCheckBox>

class QLabel;

class AnimatedCheckBox : public QCheckBox
{
public:
    AnimatedCheckBox(QWidget *parent = nullptr);

protected:
    void paintEvent(QPaintEvent *event) override;
    void resizeEvent(QResizeEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;

private:
    /* AnimatedCheckBox 是否选中的指示器
        checked 为 false 时 indicator 在最左边,为 true 时 indicator 在最右边 */
    QLabel *indicator;
};

#endif // ANIMATEDCHECKBOX_H
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

AnimatedCheckBox.cpp

#include "AnimatedCheckBox.h"

#include <QStyle>
#include <QLabel>
#include <QMouseEvent>
#include <QApplication>

AnimatedCheckBox::AnimatedCheckBox(QWidget *parent) : QCheckBox (parent)
{
    indicator = new QLabel(this);

    /* 设置样式 */
    this->setMinimumHeight(40);
    this->setAttribute(Qt::WA_StyledBackground, true);
    this->setProperty("class", "AnimatedCheckBox");
    indicator->setProperty("class", "AnimatedCheckBoxIndicator");

    /* AnimatedCheckBox 的选中状态变化时,修改 indicator 的位置 */
    connect(this, &QCheckBox::toggled, [=] {
        int x = this->isChecked() ? this->width() - indicator->width() : 0;
        int y = 0;
        indicator->move(x, y);

        /* checked 属性变化了,更新样式 */
        this->style()->polish(this);
    });
}

/* 重写 paintEvent 方法,清除 QCheckBox 的默认样式 */
void AnimatedCheckBox::paintEvent(QPaintEvent *) {}

/* AnimatedCheckBox 的大小改变时调整 indicator 的位置 */
void AnimatedCheckBox::resizeEvent(QResizeEvent *)
{
    int x = this->isChecked() ? this->width() - indicator->width(): 0;
    int y = 0;
    int w = height();
    int h = w;
    indicator->setGeometry(x, y, w, h);

    /* 设置 AnimatedCheckBox 的最小宽度,避免太窄的时候效果不好 */
    this->setMinimumWidth(height() * 2);
}

/* 点击 AnimatedCheckBox 上的任何地方都切换选中状态,QCheckBox 默认只有点击它的 indicator 或者文字时才进行切换 */
void AnimatedCheckBox::mousePressEvent(QMouseEvent *event)
{
    event->accept();
    setChecked(!isChecked());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

3.4 设置样式

QtAnimationExample.qss

.AnimatedCheckBox[checked=true ]
{
    background: #2d8cf0;
}
.AnimatedCheckBox[checked=false]
{
     background: #c5c8ce;
}
.AnimatedCheckBoxIndicator
{
    background: white;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

main.cpp

#include "QtAnimationExample.h"
#include <QtWidgets/QApplication>
#include <QFile>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    /* 设置qss */
    QFile qss(":/QtAnimationExample.qss");
    qss.open(QFile::ReadOnly);
    a.setStyleSheet(qss.readAll());
    qss.close();

    QtAnimationExample w;
    w.show();
    return a.exec();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

3.5 初步效果

在这里插入图片描述

3.6 使用动画

使用 QPropertyAnimation 给 AnimatedCheckBox 加上动画效果,设置缓冲曲线类型为QEasingCurve::InOutCubic,修改构造函数中的connect部分即可:

QPropertyAnimation *animation = new QPropertyAnimation(indicator, "pos", this);
connect(this, &QCheckBox::toggled, [=] {
    int b = this->contentsMargins().left();
    int x = this->isChecked() ? this->width() - indicator->width() - b : b;
    int y = b;

    animation->stop();
    animation->setDuration(200);
    animation->setEndValue(QPoint(x, y));
    animation->setEasingCurve(QEasingCurve::InOutCubic);
    animation->start();

    this->style()->polish(this); 
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

注意: 在动画开始前调用了 animation->stop(),是为了避免快速点击多次时前次动画没有完成影响本次动画的效果

3.7 实现圆角

在resizeEvent最后增加下面的代码实现圆角效果:

/* 更新 check box 和 indicator 的圆角大小 */
this->setStyleSheet(QString(".AnimatedCheckBox { border-radius: %1px } .AnimatedCheckBoxIndicator { border-radius: %2px }")
                    .arg(this->height() / 2)
                    .arg(indicator->height() / 2));
  • 1
  • 2
  • 3
  • 4

注意: (1)AnimatedCheckBox 和 indicator 的大小不是固定的,需要在它们的大小改变时动态的计算圆角的半径;(2)QSS 中 border-radius 的值如果大于对应边的一半时就没有圆角效果了

3.8 增加边框

QCheckBox 的 contents margins 的 left 表示边框的宽度, indicator 的位置和大小需要根据边框宽度修改为:

int b = this->contentsMargins().left();
int x = this->isChecked() ? this->width() - indicator->width() - b : b;
int y = b;
int w = height() - b - b;
int h = w;
  • 1
  • 2
  • 3
  • 4
  • 5

通过调用 setContentsMargins 来设置边框宽度:

this->setContentsMargins(2, 2, 2, 2)
  • 1

3.9 增加阴影

使用 QGraphicsDropShadowEffect 增加阴影效果:

QGraphicsDropShadowEffect *effect = new QGraphicsDropShadowEffect(this);
effect->setBlurRadius(10);
effect->setOffset(0, 1);
indicator->setGraphicsEffect(effect);
  • 1
  • 2
  • 3
  • 4

这样我们就实现了AnimatedCheckBox!

3.10 参考资料

http://qtdebug.com/qtbook-animated-checkbox/

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号