【Qt炫酷动画】1.easing官方demo详细剖析

45 篇文章 39 订阅 ¥59.90 ¥99.00
本文详细剖析了Qt中关于easing的官方demo,从项目的构建到代码实现,逐一进行解读。通过分析代码目录结构和关键实现部分,如自定义Animation类、QPropertyAnimation的使用以及QEasingCurve的插值曲线。文章还提示读者可以查看QWidget的Q_PROPERTY属性以了解如何改变不同属性,并鼓励学习createCurveIcons中绘制曲线的技巧。
摘要由CSDN通过智能技术生成


1.demo效果

在这里插入图片描述

2.demo项目构建

  • 打开Qt Creator,按照图上操作
    在这里插入图片描述
  • 使用Minggw64构建运行
    在这里插入图片描述

3.代码详细剖析

代码目录结构

 ├📁easing
 |  📄animation.h
 |  📦​easing.pro
 |  🎴​easing.qrc
 |  🎴​form.ui
 |  📄main.cpp
 |  📄window.cpp
 |  📄window.h
  ├📁images
  |  🎴​qt-logo.png

代码实现

  • /easing/animation.h
#ifndef ANIMATION_H
#define ANIMATION_H

#include <QtWidgets>

#include <QtCore/qpropertyanimation.h>

class Animation : public QPropertyAnimation {
public:
    enum PathType {
        LinearPath,
        CirclePath,
        NPathTypes
    };
    Animation(QObject *target, const QByteArray &prop)
        : QPropertyAnimation(target, prop)
    {
        setPathType(LinearPath);
    }

    //设置路径直线、圆
    void setPathType(PathType pathType)
    {
        if (pathType >= NPathTypes)
            qWarning("Unknown pathType %d", pathType);

        m_pathType = pathType;
        m_path = QPainterPath();
    }

    //每一毫秒都会调用的函数,实现这个虚函数
    void updateCurrentTime(int currentTime) override
    {
        if (m_pathType == CirclePath) {
            //圆
            if (m_path.isEmpty()) {
                //计算开始点和结束点
                QPointF to = endValue().toPointF();
                QPointF from = startValue().toPointF();
                //移动到开始点
                m_path.moveTo(from);
                //添加路径
                m_path.addEllipse(QRectF(from, to));
            }
            //总时长
            int dura = duration();

            //当前进度
            const qreal progress = ((dura == 0) ? 1 : ((((currentTime - 1) % dura) + 1) / qreal(dura)));
            //根据进度计算
            qreal easedProgress = easingCurve().valueForProgress(progress);
            if (easedProgress > 1.0) {
                easedProgress -= 1.0;
            } else if (easedProgress < 0) {
                easedProgress += 1.0;
            }
            //计算位置
            QPointF pt = m_path.pointAtPercent(easedProgress);
            //更新值并发送信号
            updateCurrentValue(pt);
            emit valueChanged(pt);
        } else {
            QPropertyAnimation::updateCurrentTime(currentTime);
        }
    }

    QPainterPath m_path;
    PathType m_pathType;
};

#endif // ANIMATION_H

  • /easing/easing.pro
QT += widgets
requires(qtConfig(listwidget))

HEADERS = window.h \
          animation.h
SOURCES = main.cpp \
          window.cpp

FORMS   = form.ui

RESOURCES = easing.qrc

# install
target.path = $$[QT_INSTALL_EXAMPLES]/widgets/animation/easing
INSTALLS += target

  • /easing/form.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Form</class>
 <widget class="QWidget" name="Form">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>545</width>
    <height>471</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Easing curves</string>
  </property>
  <layout class="QGridLayout" name="gridLayout">
   <item row="0" column="0" colspan="2">
    <widget class="QListWidget" name="easingCurvePicker">
     <property name="sizePolicy">
      <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
       <horstretch>0</horstretch>
       <verstretch>0</verstretch>
      </sizepolicy>
     </property>
     <property name="maximumSize">
      <size>
       <width>16777215</width>
       <height>120</height>
      </size>
     </property>
     <property name="verticalScrollBarPolicy">
      <enum>Qt::ScrollBarAlwaysOff</enum>
     </property>
     <property name="movement">
      <enum>QListView::Static</enum>
     </property>
     <property name="isWrapping" stdset="0">
      <bool>false</bool>
     </property>
     <property name="viewMode">
      <enum>QListView::IconMode</enum>
     </property>
     <property name="selectionRectVisible">
      <bool>false</bool>
     </property>
    </widget>
   </item>
   <item row="1" column="0">
    <layout class="QVBoxLayout" name="verticalLayout">
     <item>
      <widget class="QGroupBox" name="groupBox_2">
       <property name="maximumSize">
        <size>
         <width>16777215</width>
         <height>16777215</height>
        </size>
       </property>
       <property name="title">
        <string>Path type</string>
       </property>
       <layout class="QGridLayout" name="gridLayout_2">
        <item row="0" column="0">
         <widget class="QRadioButton" name="lineRadio">
          <property name="maximumSize">
           <size>
            <width>16777215</width>
            <height>40</height>
           </size>
          </property>
          <property name="layoutDirection">
           <enum>Qt::LeftToRight</enum>
          </property>
          <property name="text">
           <string>Line</string>
          </property>
          <property name="checked">
           <bool>true</bool>
          </property>
          <attribute name="buttonGroup">
           <string>buttonGroup</string>
          </attribute>
         </widget>
        </item>
        <item row="1" column="0">
         <widget class="QRadioButton" name="circleRadio">
          <property name="maximumSize">
           <size>
            <width>16777215</width>
            <height>40</height>
           </size>
          </property>
          <property name="text">
           <string>Circle</string>
          </property>
          <attribute name="buttonGroup">
           <string>buttonGroup</string>
          </attribute>
         </widget>
        </item>
       </layout>
      </widget>
     </item>
     <item>
      <widget class="QGroupBox" name="groupBox">
       <property name="sizePolicy">
        <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
         <horstretch>0</horstretch>
         <verstretch>0</verstretch>
        </sizepolicy>
       </property>
       <property name="title">
        <string>Properties</string>
       </property>
       <layout class="QFormLayout" name="formLayout">
        <property name="fieldGrowthPolicy">
         <enum>QFormLayout::AllNonFixedFieldsGrow</enum>
        </property>
        <item row="0" column="0">
         <widget class="QLabel" name="label">
          <property name="sizePolicy">
           <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
            <horstretch>0</horstretch>
            <verstretch>0</verstretch>
           </sizepolicy>
          </property>
          <property name="minimumSize">
           <size>
            <width>0</width>
            <height>30</height>
           </size>
          </property>
          <property name="text">
           <string>Period</string>
          </property>
         </widget>
        </item>
        <item row="0" column="1">
         <widget class="QDoubleSpinBox" name="periodSpinBox">
          <property name="enabled">
           <bool>false</bool>
          </property>
          <property name="sizePolicy">
           <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
            <horstretch>0</horstretch>
            <verstretch>0</verstretch>
           </sizepolicy>
          </property>
          <property name="minimumSize">
           <size>
            <width>0</width>
            <height>30</height>
           </size>
          </property>
          <property name="minimum">
           <double>-1.000000000000000</double>
          </property>
          <property name="singleStep">
           <double>0.100000000000000</double>
          </property>
          <property name="value">
           <double>-1.000000000000000</double>
          </property>
         </widget>
        </item>
        <item row="2" column="1">
         <widget class="QDoubleSpinBox" name="amplitudeSpinBox">
          <property name="enabled">
           <bool>false</bool>
          </property>
          <property name="minimumSize">
           <size>
            <width>0</width>
            <height>30</height>
           </size>
          </property>
          <property name="minimum">
           <double>-1.000000000000000</double>
          </property>
          <property name="singleStep">
           <double>0.100000000000000</double>
          </property>
          <property name="value">
           <double>-1.000000000000000</double>
          </property>
         </widget>
        </item>
        <item row="4" column="0">
         <widget class="QLabel" name="label_3">
          <property name="minimumSize">
           <size>
            <width>0</width>
            <height>30</height>
           </size>
          </property>
          <property name="text">
           <string>Overshoot</string>
          </property>
         </widget>
        </item>
        <item row="4" column="1">
         <widget class="QDoubleSpinBox" name="overshootSpinBox">
          <property name="enabled">
           <bool>false</bool>
          </property>
          <property name="minimumSize">
           <size>
            <width>0</width>
            <height>30</height>
           </size>
          </property>
          <property name="minimum">
           <double>-1.000000000000000</double>
          </property>
          <property name="singleStep">
           <double>0.100000000000000</double>
          </property>
          <property name="value">
           <double>-1.000000000000000</double>
          </property>
         </widget>
        </item>
        <item row="2" column="0">
         <widget class="QLabel" name="label_2">
          <property name="minimumSize">
           <size>
            <width>0</width>
            <height>30</height>
           </size>
          </property>
          <property name="text">
           <string>Amplitude</string>
          </property>
         </widget>
        </item>
       </layout>
      </widget>
     </item>
     <item>
      <spacer name="verticalSpacer">
       <property name="orientation">
        <enum>Qt::Vertical</enum>
       </property>
       <property name="sizeHint" stdset="0">
        <size>
         <width>20</width>
         <height>40</height>
        </size>
       </property>
      </spacer>
     </item>
    </layout>
   </item>
   <item row="1" column="1">
    <widget class="QGraphicsView" name="graphicsView">
     <property name="sizePolicy">
      <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
       <horstretch>0</horstretch>
       <verstretch>0</verstretch>
      </sizepolicy>
     </property>
    </widget>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
 <buttongroups>
  <buttongroup name="buttonGroup"/>
 </buttongroups>
</ui>

  • /easing/main.cpp
#include <QtWidgets>
#include "window.h"
#include <QtDebug>
int main(int argc, char **argv)
{
    Q_INIT_RESOURCE(easing);
    QApplication app(argc, argv);
    Window w;

    //固定400*400大小
    w.resize(400, 400);
    w.show();

    return app.exec();
}

  • /easing/window.cpp
#include "window.h"

Window::Window(QWidget *parent)
    : QWidget(parent),
    m_iconSize(64, 64)
{
    m_ui.setupUi(this);


    //listview设置图标显示模式
    m_ui.easingCurvePicker->setIconSize(m_iconSize);
    //设置最小高度
    m_ui.easingCurvePicker->setMinimumHeight(m_iconSize.height() + 50);

    //直线或圆设置按钮组
    m_ui.buttonGroup->setId(m_ui.lineRadio, 0);
    m_ui.buttonGroup->setId(m_ui.circleRadio, 1);


    //曲线对象
    QEasingCurve dummy;

    //周期
    m_ui.periodSpinBox->setValue(dummy.period());
    //振幅
    m_ui.amplitudeSpinBox->setValue(dummy.amplitude());
    //过冲
    m_ui.overshootSpinBox->setValue(dummy.overshoot());

    //list中被点击绑定
    connect(m_ui.easingCurvePicker, &QListWidget::currentRowChanged,
            this, &Window::curveChanged);

    //按钮组被点击
    connect(m_ui.buttonGroup, QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked),
            this, &Window::pathChanged);

    connect(m_ui.periodSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
            this, &Window::periodChanged);
    connect(m_ui.amplitudeSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
            this, &Window::amplitudeChanged);
    connect(m_ui.overshootSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
            this, &Window::overshootChanged);

    //初始化List及图标
    createCurveIcons();


    //声明图片
    QPixmap pix(QLatin1String(":/images/qt-logo.png"));

    //item对象
    m_item = new PixmapItem(pix);
    //场景添加对象
    m_scene.addItem(m_item);
    m_ui.graphicsView->setScene(&m_scene);

    //声明动画对象,赋值动画对象和 位置属性
    m_anim = new Animation(m_item, "pos");
    //设置动画曲线
    m_anim->setEasingCurve(QEasingCurve::OutBounce);

    //list默认选中第一个
    m_ui.easingCurvePicker->setCurrentRow(int(QEasingCurve::OutBounce));

    startAnimation();
}

QEasingCurve createEasingCurve(QEasingCurve::Type curveType)
{
    QEasingCurve curve(curveType);

    if (curveType == QEasingCurve::BezierSpline) {
        //贝塞尔曲线
        curve.addCubicBezierSegment(QPointF(0.4, 0.1), QPointF(0.6, 0.9), QPointF(1.0, 1.0));

    } else if (curveType == QEasingCurve::TCBSpline) {
        //TCB 样条曲线
        curve.addTCBSegment(QPointF(0.0, 0.0), 0, 0, 0);
        curve.addTCBSegment(QPointF(0.3, 0.4), 0.2, 1, -0.2);
        curve.addTCBSegment(QPointF(0.7, 0.6), -0.2, 1, 0.2);
        curve.addTCBSegment(QPointF(1.0, 1.0), 0, 0, 0);
    }

    return curve;
}

void Window::createCurveIcons()
{
    QPixmap pix(m_iconSize);
    QPainter painter(&pix);

    //渐变色
    QLinearGradient gradient(0,0, 0, m_iconSize.height());
    gradient.setColorAt(0.0, QColor(240, 240, 240));
    gradient.setColorAt(1.0, QColor(224, 224, 224));
    QBrush brush(gradient);
    const QMetaObject &mo = QEasingCurve::staticMetaObject;
    //枚举转文字
    QMetaEnum metaEnum = mo.enumerator(mo.indexOfEnumerator("Type"));

    // Skip QEasingCurve::Custom
    for (int i = 0; i < QEasingCurve::NCurveTypes - 1; ++i) {
        painter.fillRect(QRect(QPoint(0, 0), m_iconSize), brush);
        //动画曲线
        QEasingCurve curve = createEasingCurve((QEasingCurve::Type) i);
        painter.setPen(QColor(0, 0, 255, 64));
        qreal xAxis = m_iconSize.height()/1.5;
        qreal yAxis = m_iconSize.width()/3;

        //X轴、y轴
        painter.drawLine(0, xAxis, m_iconSize.width(),  xAxis);
        painter.drawLine(yAxis, 0, yAxis, m_iconSize.height());


        //比例为icon的一半
        qreal curveScale = m_iconSize.height()/2;

        painter.setPen(Qt::NoPen);

        // start point 开始点
        painter.setBrush(Qt::red);


        QPoint start(yAxis, xAxis - curveScale * curve.valueForProgress(0));
        painter.drawRect(start.x() - 1, start.y() - 1, 3, 3);

        // end point 结束点
        painter.setBrush(Qt::blue);
        QPoint end(yAxis + curveScale, xAxis - curveScale * curve.valueForProgress(1));
        painter.drawRect(end.x() - 1, end.y() - 1, 3, 3);

        //绘制路径
        QPainterPath curvePath;
        curvePath.moveTo(start);
        for (qreal t = 0; t <= 1.0; t+=1.0/curveScale) {
            QPoint to;
            to.setX(yAxis + curveScale * t);
            to.setY(xAxis - curveScale * curve.valueForProgress(t));
            curvePath.lineTo(to);
        }

        //绘制曲线名称
        painter.setRenderHint(QPainter::Antialiasing, true);
        painter.strokePath(curvePath, QColor(32, 32, 32));
        painter.setRenderHint(QPainter::Antialiasing, false);
        QListWidgetItem *item = new QListWidgetItem;
        item->setIcon(QIcon(pix));
        item->setText(metaEnum.key(i));
        m_ui.easingCurvePicker->addItem(item);
    }
}

void Window::startAnimation()
{
    //开启动画
    m_anim->setStartValue(QPointF(0, 0));
    m_anim->setEndValue(QPointF(100, 100));
    m_anim->setDuration(2000);
    m_anim->setLoopCount(-1); // forever
    m_anim->start();
}

void Window::curveChanged(int row)
{
    //被点击的曲线
    QEasingCurve::Type curveType = (QEasingCurve::Type)row;
    m_anim->setEasingCurve(createEasingCurve(curveType));
    m_anim->setCurrentTime(0);

    bool isElastic = curveType >= QEasingCurve::InElastic && curveType <= QEasingCurve::OutInElastic;
    bool isBounce = curveType >= QEasingCurve::InBounce && curveType <= QEasingCurve::OutInBounce;

    //判断是否存振幅、周期和过冲
    m_ui.periodSpinBox->setEnabled(isElastic);
    m_ui.amplitudeSpinBox->setEnabled(isElastic || isBounce);
    m_ui.overshootSpinBox->setEnabled(curveType >= QEasingCurve::InBack && curveType <= QEasingCurve::OutInBack);
}

void Window::pathChanged(QAbstractButton *button)
{

    const int index = m_ui.buttonGroup->id(button);
    //设置动画对象 是圆还是直线
    m_anim->setPathType(Animation::PathType(index));
}

void Window::periodChanged(double value)
{
    QEasingCurve curve = m_anim->easingCurve();
    curve.setPeriod(value);
    m_anim->setEasingCurve(curve);
}

void Window::amplitudeChanged(double value)
{
    QEasingCurve curve = m_anim->easingCurve();
    curve.setAmplitude(value);
    m_anim->setEasingCurve(curve);
}

void Window::overshootChanged(double value)
{
    QEasingCurve curve = m_anim->easingCurve();
    curve.setOvershoot(value);
    m_anim->setEasingCurve(curve);
}


  • /easing/window.h
#include <QtWidgets>

#include "ui_form.h"
#include "animation.h"

class PixmapItem : public QObject, public QGraphicsPixmapItem
{
    Q_OBJECT
    Q_PROPERTY(QPointF pos READ pos WRITE setPos)
public:
    PixmapItem(const QPixmap &pix) : QGraphicsPixmapItem(pix)
    {
    }
};

class Window : public QWidget {
    Q_OBJECT
public:
    Window(QWidget *parent = nullptr);

private slots:
    //动画曲线修改
    void curveChanged(int row);

    //路径变化
    void pathChanged(QAbstractButton *button);

    //振幅改变处理
    void periodChanged(double);
    //周期改变处理
    void amplitudeChanged(double);
    //过冲处理
    void overshootChanged(double);

private:
    //创建曲线对应的icon,放到list中
    void createCurveIcons();
    //开启动画
    void startAnimation();

    Ui::Form m_ui;
    //场景对象
    QGraphicsScene m_scene;
    //场景中的元素
    PixmapItem *m_item;
    //自定义动画对象
    Animation *m_anim;
    //listview的icon大小
    QSize m_iconSize;
};

4.实现过程分析

  • 官方demo使用自定义的Animation继承QPropertyAnimation,实现了updateCurrentTime虚函数来实现圆的动作。

  • setEasingCurve对动画类设置查直曲线,其中曲线有多种类型见QEasingCurve类中枚举

  • new Animation(m_item, “pos”);时候指定了pos属性,这是QWidget中的属性,如果要改变别的属性,怎么查。

    我们可以看QWidget头文件中使用Q_PROPERTY的属性即可。

  • createCurveIcons中绘制模拟插值曲线的实现很精妙,大家可以抄袭。

  • QEasingCurve内置很多插值曲线,但是常用的就几种

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程小鱼酱

用心写好每一篇博客

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值