Qt Animation Framework

QPropertyAnimation, QSequentialAnimationGroup, QParallelAnimationGroup, easing curves — animating Qt widgets and custom properties.

5 min read
44698 chars

Why Animate in Qt?

Smooth animations make embedded HMIs and desktop apps feel professional. Qt’s animation framework works by animating Qt properties (Q_PROPERTY) over time using easing curves.

QAbstractAnimation
  ├── QVariantAnimation
  │     └── QPropertyAnimation     ← animates Q_PROPERTY values
  ├── QPauseAnimation               ← delay in a sequence
  └── QAnimationGroup
        ├── QSequentialAnimationGroup  ← run one after another
        └── QParallelAnimationGroup    ← run all simultaneously

QPropertyAnimation — Basics

#include <QPropertyAnimation>

QPushButton *btn = new QPushButton("Connect", this);
btn->setGeometry(0, 200, 120, 40);

// Animate the geometry property
QPropertyAnimation *anim = new QPropertyAnimation(btn, "geometry");
anim->setDuration(600);                              // 600 ms
anim->setStartValue(QRect(0,   200, 120, 40));
anim->setEndValue  (QRect(200, 200, 120, 40));
anim->setEasingCurve(QEasingCurve::OutCubic);
anim->start(QAbstractAnimation::DeleteWhenStopped);  // auto-delete

Common Animatable Properties

WidgetPropertyType
QWidgetgeometryQRect
QWidgetposQPoint
QWidgetsizeQSize
QWidgetwindowOpacityqreal (0.0–1.0)
QGraphicsItemposQPointF
CustomAny Q_PROPERTYAny variant type

Easing Curves

// Linear — constant speed
anim->setEasingCurve(QEasingCurve::Linear);

// Ease in/out (smooth)
anim->setEasingCurve(QEasingCurve::InOutQuad);

// Bounce at end
anim->setEasingCurve(QEasingCurve::OutBounce);

// Elastic overshoot
anim->setEasingCurve(QEasingCurve::OutElastic);

// Back (slight overshoot)
anim->setEasingCurve(QEasingCurve::OutBack);

Easing Curve Shapes:

Linear:       ──────────────────
InOutQuad:    ──╱────────────╲──
OutBounce:    ────────────╱╲╱╲──
OutElastic:   ─────────────╱~╲─
OutBack:      ──────────────╱─╲

Sequential Animation Group

QSequentialAnimationGroup *seq = new QSequentialAnimationGroup(this);

// 1) Fade in
QPropertyAnimation *fadeIn = new QPropertyAnimation(widget, "windowOpacity");
fadeIn->setDuration(300);
fadeIn->setStartValue(0.0);
fadeIn->setEndValue(1.0);

// 2) Pause 500ms
QPauseAnimation *pause = new QPauseAnimation(500);

// 3) Slide right
QPropertyAnimation *slide = new QPropertyAnimation(widget, "pos");
slide->setDuration(400);
slide->setStartValue(QPoint(0, 100));
slide->setEndValue  (QPoint(300, 100));
slide->setEasingCurve(QEasingCurve::OutQuart);

seq->addAnimation(fadeIn);
seq->addAnimation(pause);
seq->addAnimation(slide);
seq->start();

connect(seq, &QSequentialAnimationGroup::finished, [](){
    qDebug() << "Animation sequence complete";
});

Parallel Animation Group

QParallelAnimationGroup *par = new QParallelAnimationGroup(this);

// Both run at the same time
QPropertyAnimation *grow = new QPropertyAnimation(btn, "size");
grow->setDuration(500);
grow->setStartValue(QSize(80, 30));
grow->setEndValue  (QSize(160, 60));
grow->setEasingCurve(QEasingCurve::OutQuint);

QPropertyAnimation *brighten = new QPropertyAnimation(btn, "windowOpacity");
brighten->setDuration(500);
brighten->setStartValue(0.3);
brighten->setEndValue(1.0);

par->addAnimation(grow);
par->addAnimation(brighten);
par->start();

Animating Custom Properties

class StatusIndicator : public QWidget {
    Q_OBJECT
    Q_PROPERTY(float glow READ glow WRITE setGlow NOTIFY glowChanged)
    float m_glow = 0.0f;

public:
    float glow() const { return m_glow; }
    void  setGlow(float g) { m_glow = g; emit glowChanged(g); update(); }

signals:
    void glowChanged(float g);

    void pulseAnimate() {
        QPropertyAnimation *pulse = new QPropertyAnimation(this, "glow");
        pulse->setDuration(800);
        pulse->setStartValue(0.0f);
        pulse->setEndValue(1.0f);
        pulse->setEasingCurve(QEasingCurve::InOutSine);
        pulse->setLoopCount(-1);  // infinite
        pulse->start();
    }

protected:
    void paintEvent(QPaintEvent *) override {
        QPainter p(this);
        p.setRenderHint(QPainter::Antialiasing);
        QRadialGradient grad(rect().center(), width() / 2.0);
        grad.setColorAt(0.0, QColor(166, 227, 161, static_cast<int>(255 * m_glow)));
        grad.setColorAt(1.0, Qt::transparent);
        p.fillRect(rect(), grad);
        p.setBrush(QColor("#a6e3a1"));
        p.setPen(Qt::NoPen);
        p.drawEllipse(rect().center(), 8, 8);
    }
};

Visual: Animation Timeline

QSequentialAnimationGroup (total: 1200ms)
│
├─ fadeIn  [0ms ─────────── 300ms]
│
├─ pause   [300ms ─────────── 800ms]
│
└─ slide   [800ms ─────────── 1200ms]

QParallelAnimationGroup (total: 500ms — max of children)
│
├─ grow     [0ms ─────────── 500ms]
└─ brighten [0ms ─────────── 500ms]

Lab 1 — Animated Notification Toast

class Toast : public QLabel {
    Q_OBJECT
public:
    static void show(QWidget *parent, const QString &msg,
                     int durationMs = 2500)
    {
        auto *toast = new Toast(msg, parent);
        toast->show();

        auto *seq = new QSequentialAnimationGroup(toast);

        // Slide in from bottom
        auto *slideIn = new QPropertyAnimation(toast, "pos");
        slideIn->setDuration(300);
        slideIn->setStartValue(QPoint(20, parent->height()));
        slideIn->setEndValue  (QPoint(20, parent->height() - 60));
        slideIn->setEasingCurve(QEasingCurve::OutCubic);

        // Wait
        seq->addAnimation(slideIn);
        seq->addAnimation(new QPauseAnimation(durationMs));

        // Fade out
        auto *fadeOut = new QPropertyAnimation(toast, "windowOpacity");
        fadeOut->setDuration(300);
        fadeOut->setStartValue(1.0);
        fadeOut->setEndValue(0.0);
        seq->addAnimation(fadeOut);

        QObject::connect(seq, &QSequentialAnimationGroup::finished,
                         toast, &QObject::deleteLater);
        seq->start();
    }

private:
    Toast(const QString &msg, QWidget *parent)
        : QLabel(msg, parent)
    {
        setStyleSheet("background:#313244; color:#cdd6f4; "
                      "border-radius:8px; padding:8px 16px;");
        adjustSize();
        setWindowOpacity(1.0);
    }
};

// Usage
QPushButton *btn = new QPushButton("Connect");
connect(btn, &QPushButton::clicked, [=](){
    Toast::show(this, "Device connected successfully!");
});

Lab 2 — Loading Spinner

class Spinner : public QWidget {
    Q_OBJECT
    Q_PROPERTY(float angle READ angle WRITE setAngle)
    float m_angle = 0.0f;

public:
    Spinner(QWidget *p = nullptr) : QWidget(p) {
        setFixedSize(48, 48);
        QPropertyAnimation *spin = new QPropertyAnimation(this, "angle");
        spin->setDuration(1000);
        spin->setStartValue(0.0f);
        spin->setEndValue(360.0f);
        spin->setLoopCount(-1);
        spin->start();
    }

    float angle() const { return m_angle; }
    void  setAngle(float a) { m_angle = a; update(); }

protected:
    void paintEvent(QPaintEvent *) override {
        QPainter p(this);
        p.setRenderHint(QPainter::Antialiasing);
        p.translate(width()/2, height()/2);
        p.rotate(m_angle);
        int n = 8;
        for (int i = 0; i < n; ++i) {
            float alpha = 255 * (i + 1) / n;
            p.setPen(QPen(QColor(137, 180, 250, static_cast<int>(alpha)), 3,
                         Qt::SolidLine, Qt::RoundCap));
            p.drawLine(0, 8, 0, 16);
            p.rotate(360.0 / n);
        }
    }
};

Interview Questions

Q1: What types can be animated with QPropertyAnimation?

Any Qt property type that is registered with Q_PROPERTY and is a QVariant-compatible type: int, float, double, QRect, QPoint, QSize, QColor, qreal, etc. Custom types need a registered interpolation function.

Q2: What happens when you call start() on an already-running animation?

The animation stops and restarts from the beginning (or current start value). To resume from the current position, use pause() and resume().

Q3: How do you make an animation repeat forever?

Call setLoopCount(-1). Use setLoopCount(n) for a specific number of repeats.

Q4: What is the difference between QSequentialAnimationGroup and QParallelAnimationGroup?

Sequential runs animations one after another. Parallel runs them simultaneously. You can nest them — e.g., a sequential group containing a parallel group.

Q5: How do you animate a custom property (not a built-in one)?

Declare it with Q_PROPERTY in a QObject subclass, providing READ and WRITE accessors. The WRITE slot should call update() to repaint if it affects appearance. QPropertyAnimation will then interpolate the property automatically.


Application: Animated HMI Dashboard

class AnimatedDashboard : public QWidget {
    Q_OBJECT
public:
    AnimatedDashboard(QWidget *p = nullptr) : QWidget(p) {
        setWindowTitle("Animated Dashboard");
        resize(800, 500);
        setStyleSheet("background:#1e1e2e;");

        auto *lay = new QHBoxLayout(this);

        // 4 gauge cards that animate on startup
        QList<QWidget*> cards;
        for (auto &title : QStringList{"Temp", "Voltage", "Humidity", "Pressure"}) {
            auto *card = new QFrame;
            card->setStyleSheet(
                "QFrame{ background:#313244; border-radius:12px; }");
            card->setFixedSize(160, 200);
            auto *cardLay = new QVBoxLayout(card);
            auto *lbl = new QLabel(title);
            lbl->setAlignment(Qt::AlignCenter);
            lbl->setStyleSheet("color:#cdd6f4; font-size:14px;");
            auto *spinner = new Spinner(card);
            cardLay->addWidget(spinner, 0, Qt::AlignCenter);
            cardLay->addWidget(lbl);
            lay->addWidget(card);
            cards.append(card);
        }

        // Stagger entry animations
        for (int i = 0; i < cards.size(); ++i) {
            auto *card = cards[i];
            card->setWindowOpacity(0.0);

            QTimer::singleShot(i * 150, [card, i](){
                auto *fadeIn = new QPropertyAnimation(card, "windowOpacity");
                fadeIn->setDuration(400);
                fadeIn->setStartValue(0.0);
                fadeIn->setEndValue(1.0);
                fadeIn->setEasingCurve(QEasingCurve::OutCubic);

                auto *riseUp = new QPropertyAnimation(card, "pos");
                riseUp->setDuration(400);
                riseUp->setStartValue(card->pos() + QPoint(0, 40));
                riseUp->setEndValue(card->pos());
                riseUp->setEasingCurve(QEasingCurve::OutCubic);

                auto *par = new QParallelAnimationGroup;
                par->addAnimation(fadeIn);
                par->addAnimation(riseUp);
                par->start(QAbstractAnimation::DeleteWhenStopped);
            });
        }
    }
};

References

ResourceLink
Qt Docs: Animation Frameworkhttps://doc.qt.io/qt-6/animation-overview.html
Qt Docs: QPropertyAnimationhttps://doc.qt.io/qt-6/qpropertyanimation.html
Qt Docs: QEasingCurvehttps://doc.qt.io/qt-6/qeasingcurve.html
Qt Docs: QAnimationGrouphttps://doc.qt.io/qt-6/qanimationgroup.html

Next tutorial → Qt Styling & Themes