Qt自定義動畫插值函式

兜尼完發表於2024-03-06

這篇文章展示瞭如何使用自定義的型別實現動畫。自定義型別的動畫效果使用QVariantAnimation類實現,需要實現一個插值函式。Qt提供了多個介面,主要有如下兩種方法:

  1. 實現QVariant QVariantAnimation::interpolated(const QVariant &from, const QVariant &to, qreal progress) const函式;
  2. 使用template <typename T> void qRegisterAnimationInterpolator(QVariant (*func)(const T &, const T &, qreal))函式或void QVariantAnimation::registerInterpolator(Interpolator func, int interpolationType)函式註冊自定義插值函式。

本例使用第一種方法,實現了一個按鈕在兩點之間沿著圓弧從起始點運動到結束點的效果。截圖示意如下:

標頭檔案。自定義型別是MMyType,我們重寫了interpolated(...)函式:

struct MMyType
{
    QPointF pos;
};

Q_DECLARE_METATYPE(MMyType);

class MDiyAnimation : public QVariantAnimation
{
    Q_OBJECT

public:
    MDiyAnimation(QObject *parent = 0);

private:
    QVariant interpolated(const QVariant &from, const QVariant &to, qreal progress) const override;
};

CPP檔案:

MDiyAnimation::MDiyAnimation(QObject *parent) : 
    QVariantAnimation(parent)
{
}

QVariant MDiyAnimation::interpolated(const QVariant &from, const QVariant &to, qreal progress) const
{
    MMyType ifrom = from.value<MMyType>();
    MMyType ito = to.value<MMyType>();

    QPointF ftvec = ifrom.pos - ito.pos;
    qreal radius = 0.5 * qSqrt(QPointF::dotProduct(ftvec, ftvec));
    QPointF center = (ifrom.pos + ito.pos) * 0.5;
    qreal angle = qAtan2(ftvec.y(), ftvec.x()) + M_PI * progress;
    MMyType newPos;
    newPos.pos.setX(center.x() + radius * qCos(angle));
    newPos.pos.setY(center.y() + radius * qSin(angle));
    return QVariant::fromValue<MMyType>(newPos);
}

在主視窗建構函式使用如下。程式碼中QtTest是主視窗類,ui.pbTest是按鈕控制元件:

QtTest::QtTest(QWidget *parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);

    MDiyAnimation* ani = new MDiyAnimation(this);
    connect(ani, &MDiyAnimation::valueChanged, this, [this](const QVariant& v) 
    {
        QPointF pp = v.value<MMyType>().pos;
        ui.pbTest->setGeometry(pp.x(), pp.y(), ui.pbTest->width(), ui.pbTest->height());
    });
    ani->setStartValue(QVariant::fromValue<MMyType>({ ui.pbTest->pos() }));
    ani->setEndValue(QVariant::fromValue<MMyType>({ QPointF(400, 400) }));
    ani->setDuration(5000);
    ani->setLoopCount(-1);
    ani->start();
}

相關文章