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

文章目录

  • 1.demo效果
  • 2.demo项目构建
  • 3.代码详细剖析
    • 代码目录结构
    • 代码实现
  • 4.实现过程分析


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.cppFORMS   = form.uiRESOURCES = 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::Customfor (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); // foreverm_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_OBJECTQ_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内置很多插值曲线,但是常用的就几种

本文链接:https://my.lmcjl.com/post/12610.html

展开阅读全文

4 评论

留下您的评论.