Qt 动画框架(11):Easing Curves Example 【官例】

前言

Easing Curves Example 显示了如何使用缓和曲线来控制动画的速度。官方提供的动画案例,也就这个比较贴近于动画框架。其他的案例可能要放到其他框架中了,等梳理完其他部分的框架后,再来翻译或者注释代码。

个案例涉及了一些Qt 方面的其他知识:属性系统、元对象系统、视图框架、Qt 2D绘图、QListWidget,但是重点是学习在动画中使用QEasingCurve以及自定义属性动画的方法。

方使用的是界面文件,出于学习的目的以及发博客的便利,界面改用代码实现。
源代码资源详见Qt安装目录: ..\Qt5_15\Examples\Qt-5.15.0\widgets\animation\easing\

以下是效果图:

Animation.hpp

#include <QtWidgets>

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();
    }

    // 每次动画的currentTime更改时都会调用此函数。
    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;
};

Window.h

包含一个QGraphicsPixmapItem的子类PixmapItem

#include <QtWidgets>
#include "Animation.hpp"

// QGraphicsPixmapItem是视图框架中的 Item
class PixmapItem : public QObject,public QGraphicsPixmapItem{
    Q_OBJECT
    // 参考 The Property System文档,pos本不是QGraphicsPixmapItem具有的属性
    // 通过这种宏使pos变量称为了PixmapItem的属性,可用于元对象,此处用作属性动画
    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:
    //为QListWidget创建Icon
    void createCurveIcons();
    // 启动动画
    void startAnimation();

    // QGraphicsScene是视图框架中的Item的容器
    QGraphicsScene m_scene;
    PixmapItem *m_item;
    Animation *m_anim;
    //QListWidget包含的Item的Icon的大小
    QSize m_iconSize;

private:
    // 列表控件
    QListWidget *easingCurvePicker;
    // 按钮组,用处使代码简单一些
    QButtonGroup *buttonGroup;
    // QEaseingCurve的几个常见属性的控件
    QDoubleSpinBox *periodSpinBox;      //周期
    QDoubleSpinBox *amplitudeSpinBox;   //幅值
    QDoubleSpinBox *overshootSpinBox;   //超调量
};

Window.cpp

#include "Window.h"

Window::Window(QWidget *parent)
    : QWidget(parent),
      m_iconSize(64,64)
{
    // QListWidget ,是基于Item的控件部分,不是主要目的
    // 但是关于QListWiget部分很好地展示了使用设定图标的用法
    easingCurvePicker = new QListWidget;
    easingCurvePicker->setIconSize(m_iconSize); //设置图标大小

    easingCurvePicker->setMaximumSize(QSize(16777215, 120)); // 主要限制了最大高度120
    easingCurvePicker->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);  //垂直滚动条不用
    easingCurvePicker->setFlow(QListView::LeftToRight);  // 设置流向
    //    easingCurvePicker->setProperty("isWrapping", QVariant(false));  //将对象的 isWrapping 属性的值设置为 false。
    easingCurvePicker->setWrapping(false);      // 这行代码代替上面一行

    //设置QListView的查看模式。
    //设置查看模式将根据传入的参数移动启用或禁用拖放。 对于ListMode,默认移动为“Static”(禁用拖放); 对于IconMode,默认移动为Free(启用拖放)。
    easingCurvePicker->setViewMode(QListView::IconMode);
    easingCurvePicker->setMovement(QListView::Static); //设置移动“Static”(禁用拖放)

    QRadioButton *lineRadio = new QRadioButton("lineRadio");
    lineRadio->setChecked(true);    //设置默认值
    QRadioButton *circleRadio = new QRadioButton("circleRadio");

    QGroupBox *group2 = new QGroupBox("Path Type");
    QVBoxLayout *group2Layout = new QVBoxLayout(group2);
    group2Layout->addWidget(lineRadio);
    group2Layout->addWidget(circleRadio);

    buttonGroup = new QButtonGroup(group2);
    buttonGroup->addButton(lineRadio,0);
    buttonGroup->addButton(circleRadio,1);

    QGroupBox *group = new QGroupBox("Properties");
    QGridLayout *groupLayout = new QGridLayout(group);

    groupLayout->addWidget(new QLabel("Period"),0,0,1,1);
    periodSpinBox = new QDoubleSpinBox;
    groupLayout->addWidget(periodSpinBox,0,1,1,1);

    groupLayout->addWidget(new QLabel("Amplitude"),1,0,1,1);
    amplitudeSpinBox = new QDoubleSpinBox;
    groupLayout->addWidget(amplitudeSpinBox,1,1,1,1);

    groupLayout->addWidget(new QLabel("Overshoot"),2,0,1,1);
    overshootSpinBox = new QDoubleSpinBox;
    groupLayout->addWidget(overshootSpinBox,2,1,1,1);

    QVBoxLayout *groups = new QVBoxLayout;
    groups->addWidget(group2);
    groups->addWidget(group);
    groups->addStretch();

    QGraphicsView *graphicsView = new QGraphicsView;

    QGridLayout *mainLayout = new QGridLayout(this);
    mainLayout->addWidget(easingCurvePicker,0,0,1,2);
    mainLayout->addLayout(groups,1,0,1,1);
    mainLayout->addWidget(graphicsView,1,1,2,1);

    QEasingCurve dummy; //默认为线性缓动曲线
    periodSpinBox->setValue(dummy.period());
    amplitudeSpinBox->setValue(dummy.amplitude());
    overshootSpinBox->setValue(dummy.overshoot());

    connect(easingCurvePicker,&QListWidget::currentRowChanged,
            this,&Window::curveChanged);
    connect(buttonGroup,QOverload<QAbstractButton*>::of(&QButtonGroup::buttonClicked),
            this,&Window::pathChanged);
    connect(periodSpinBox,QOverload<double>::of(&QDoubleSpinBox::valueChanged),
            this,&Window::periodChanged);
    connect(amplitudeSpinBox,QOverload<double>::of(&QDoubleSpinBox::valueChanged),
            this,&Window::amplitudeChanged);
    connect(overshootSpinBox,QOverload<double>::of(&QDoubleSpinBox::valueChanged),
            this,&Window::overshootChanged);

    // 为QListWidget创建图标
    createCurveIcons();

    // 视图框架部分
    QPixmap pix("images/qt-logo.png");
    m_item  = new PixmapItem(pix);
    m_scene.addItem(m_item);
    graphicsView->setScene(&m_scene);

    // 重点就是这个 动画
    m_anim = new Animation(m_item,"pos");
    m_anim->setEasingCurve(QEasingCurve::OutBounce);
    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) {
        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::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;
    periodSpinBox->setEnabled(isElastic);
    amplitudeSpinBox->setEnabled(isElastic || isBounce);
    overshootSpinBox->setEnabled(curveType >= QEasingCurve::InBack && curveType <= QEasingCurve::OutInBack);
}

void Window::pathChanged(QAbstractButton *button)
{
    const int index = 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);
}

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;

    // 返回具有给定索引的枚举数的元数据。
    // int QMetaObject::indexOfEnumerator(const char *name) const
    QMetaEnum metaEnum = mo.enumerator(mo.indexOfEnumerator("Type"));

    // 跳过 QEasingCurve::Custom 这个类型的曲线
    for (int i = 0; i < QEasingCurve::NCurveTypes - 1; ++i)
    {
        // 用渐变色填充背景
        painter.fillRect(QRect(QPoint(0, 0), m_iconSize), brush);
        // 用i做参数,迭代每种缓速曲线
        QEasingCurve curve = createEasingCurve((QEasingCurve::Type) i);

        painter.setPen(QColor(0, 0, 255, 64));
        // 画x轴 和 y轴
        qreal xAxis = m_iconSize.height()/1.5;
        qreal yAxis = m_iconSize.width()/3;
        painter.drawLine(0, xAxis, m_iconSize.width(),  xAxis);
        painter.drawLine(yAxis, 0, yAxis, m_iconSize.height());

//        qreal curveScale = m_iconSize.height();
        qreal curveScale = m_iconSize.height()/2;

        painter.setPen(Qt::NoPen);

        // 起点 红色
        painter.setBrush(Qt::red);
        QPoint start(yAxis, xAxis - curveScale * curve.valueForProgress(0));
        painter.drawRect(start.x() - 1, start.y() - 1, 3, 3);

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

        // 绘制路径,类似于各种图像的超类,Java中的Shape
        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, QColorConstants::Green);
        painter.setRenderHint(QPainter::Antialiasing, false);
        QListWidgetItem *item = new QListWidgetItem;
        item->setIcon(QIcon(pix));
        item->setText( QString("%1:").arg(i+1) + QString( metaEnum.key(i)));
        // QListWidget添加项目
        easingCurvePicker->addItem(item);
    }
}

// 设置动画的起始和终点值、持续时间、循环次数
void Window::startAnimation()
{
    m_anim->setStartValue(QPointF(0,0));
    m_anim->setEndValue(QPointF(100,100));
    m_anim->setDuration(2000);  // 2 秒
    m_anim->setLoopCount(-1);   // 反复循环
    m_anim->start();
}

mian.cpp

#include "Window.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Window w;
    w.resize(500,400);
    w.show();
    return a.exec();
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值