Auf dieser Seite

Blöcke verschieben

Das Beispiel „Blöcke verschieben“ zeigt, wie man Elemente in einem „ QGraphicsScene “ mithilfe eines „ QStateMachine “ mit einem benutzerdefinierten Übergang animieren kann.

Vier blaue Quadrate auf schwarzem Hintergrund

Das Beispiel animiert die blauen Blöcke, die Sie im obigen Bild sehen können. Die Animation bewegt die Blöcke zwischen vier voreingestellten Positionen hin und her.

Das Beispiel besteht aus den folgenden Klassen:

  • StateSwitcher erbt von „ QState “ und kann „ StateSwitchTransitions“ zu anderen Zuständen hinzufügen. Beim Aufrufen wechselt es zufällig in einen dieser Zustände.
  • StateSwitchTransition ist ein benutzerdefinierter Übergang, der bei „ StateSwitchEvents“ ausgelöst wird.
  • StateSwitchEvent ist ein „ QEvent “, der „ StateSwitchTransitions“ auslöst.
  • QGraphicsRectWidget ist ein „ QGraphicsWidget “, der lediglich seinen Hintergrund in einer einheitlichen „ blue “-Farbe ausfüllt.

Die Blöcke sind Instanzen von „ QGraphicsRectWidget “ und werden in einem „ QGraphicsScene “ animiert. Dazu erstellen wir einen Zustandsgraphen, in den wir Animationen einfügen. Der Graph wird anschließend in einem „ QStateMachine “ ausgeführt. All dies geschieht in „ main() “. Schauen wir uns zunächst die Funktion „ main() “ an.

Die Funktion „ main() “

Nachdem „ QApplication “ initialisiert wurde, richten wir den „ QGraphicsScene “ mit seinen „ QGraphicsRectWidgets“ ein.

    auto button1 = new QGraphicsRectWidget;
    auto button2 = new QGraphicsRectWidget;
    auto button3 = new QGraphicsRectWidget;
    auto button4 = new QGraphicsRectWidget;
    button2->setZValue(1);
    button3->setZValue(2);
    button4->setZValue(3);
    QGraphicsScene scene(0, 0, 300, 300);
    scene.setBackgroundBrush(Qt::black);
    scene.addItem(button1);
    scene.addItem(button2);
    scene.addItem(button3);
    scene.addItem(button4);

Nachdem die Szene zu einem „ QGraphicsView “ hinzugefügt wurde, ist es an der Zeit, den Zustandsgraphen zu erstellen. Schauen wir uns zunächst ein Zustandsdiagramm dessen an, was wir erstellen möchten.

StateSwitcher zur Steuerung von Übergängen im Gruppenstatus mit State1, State2 und State3

Beachten Sie, dass der „ group “ sieben Unterzustände hat, wir jedoch nur drei davon in das Diagramm aufgenommen haben. Der Code, der diesen Graphen erstellt, wird Zeile für Zeile untersucht und zeigt, wie der Graph funktioniert. Zunächst konstruieren wir den Zustand „ group “:

    QStateMachine machine;

    auto group = new QState;
    group->setObjectName("group");
    QTimer timer;
    timer.setInterval(1250);
    timer.setSingleShot(true);
    QObject::connect(group, &QState::entered, &timer, QOverload<>::of(&QTimer::start));

Der Timer dient dazu, zwischen jedem Verschieben der Blöcke eine Verzögerung einzufügen. Der Timer wird gestartet, wenn der Zustand „ group “ erreicht wird. Wie wir später sehen werden, verfügt „ group “ über einen Übergang zurück zum Zustand „ StateSwitcher “, wenn der Timer abgelaufen ist. „ group “ ist der Anfangszustand der Maschine, sodass beim Start des Beispiels eine Animation geplant wird.

    auto state1 = createGeometryState(button1, QRect(100, 0, 50, 50),
                                      button2, QRect(150, 0, 50, 50),
                                      button3, QRect(200, 0, 50, 50),
                                      button4, QRect(250, 0, 50, 50),
                                      group);
    ...
    auto state7 = createGeometryState(button1, QRect(0, 0, 50, 50),
                                      button2, QRect(250, 0, 50, 50),
                                      button3, QRect(0, 250, 50, 50),
                                      button4, QRect(250, 250, 50, 50),
                                      group);
    group->setInitialState(state1);

createGeometryState() gibt einen „ QState “ zurück, der beim Eintritt die Geometrie unserer Elemente festlegt. Außerdem wird „ group “ als übergeordneter Zustand dieses Zustands zugewiesen.

Ein in einen Übergang eingefügtes „ QPropertyAnimation “ verwendet die einem „ QState “ zugewiesenen Werte (mit ` QState::assignProperty()`), d. h., die Animation interpoliert zwischen den aktuellen Werten der Eigenschaften und den Werten im Zielzustand. Wir fügen dem Zustandsgraphen später animierte Übergänge hinzu.

    QParallelAnimationGroup animationGroup;

    auto anim = new QPropertyAnimation(button4, "geometry");
    anim->setDuration(1000);
    anim->setEasingCurve(QEasingCurve::OutElastic);
    animationGroup.addAnimation(anim);

Wir verschieben die Elemente parallel. Jedes Element wird zu „ animationGroup “ hinzugefügt, der Animation, die in die Übergänge eingefügt wird.

    auto subGroup = new QSequentialAnimationGroup(&animationGroup);
    subGroup->addPause(100);
    anim = new QPropertyAnimation(button3, "geometry");
    anim->setDuration(1000);
    anim->setEasingCurve(QEasingCurve::OutElastic);
    subGroup->addAnimation(anim);

Die sequenzielle Animationsgruppe „ subGroup “ hilft uns dabei, eine Verzögerung zwischen den Animationen der einzelnen Elemente einzufügen.

    auto stateSwitcher = new StateSwitcher(&machine);
    stateSwitcher->setObjectName("stateSwitcher");
    group->addTransition(&timer, &QTimer::timeout, stateSwitcher);
    stateSwitcher->addState(state1, &animationGroup);
    stateSwitcher->addState(state2, &animationGroup);
    ...
    stateSwitcher->addState(state7, &animationGroup);

In „ StateSwitcher::addState() “ wird dem Zustandsumschalter ein „StateSwitchTransition“ hinzugefügt. Außerdem fügen wir in dieser Funktion die Animation ein. Da „ QPropertyAnimation “ die Werte aus den Zuständen verwendet, können wir in allen „ StateSwitchTransition“ dieselbe „ QPropertyAnimation “-Instanz einfügen.

Wie bereits erwähnt, fügen wir dem Zustandsumschalter einen Übergang hinzu, der ausgelöst wird, wenn der Timer abläuft.

    machine.addState(group);
    machine.setInitialState(group);
    machine.start();

Abschließend können wir die Zustandsmaschine erstellen, unseren Anfangszustand hinzufügen und die Ausführung des Zustandsgraphen starten.

Die Funktion „ createGeometryState() “

In ` createGeometryState()` richten wir die Geometrie für jedes Grafikelement ein.

static QState *createGeometryState(QObject *w1, const QRect &rect1, QObject *w2, const QRect &rect2,
                                   QObject *w3, const QRect &rect3, QObject *w4, const QRect &rect4,
                                   QState *parent)
{
    auto result = new QState(parent);
    result->assignProperty(w1, "geometry", rect1);
    result->assignProperty(w2, "geometry", rect2);
    result->assignProperty(w3, "geometry", rect3);
    result->assignProperty(w4, "geometry", rect4);

    return result;
}

Wie bereits erwähnt, richtet „ QAbstractTransition “ eine mit „ addAnimation()“ hinzugefügte Animation anhand der mit „ assignProperty()“ festgelegten Eigenschaftswerte ein.

Die Klasse „StateSwitcher“

StateSwitcher verfügt über Zustandswechsel-Übergänge zu jedem der von uns mit ` createGeometryState()` erstellten ` QState`-Objekte. Ihre Aufgabe ist es, beim Aufruf zufällig zu einem dieser Zustände überzugehen.

Alle Funktionen in „ StateSwitcher “ sind inline definiert. Wir werden die Definition Schritt für Schritt durchgehen.

class StateSwitcher : public QState
{
    Q_OBJECT
public:
    explicit StateSwitcher(QStateMachine *machine) : QState(machine) { }

StateSwitcher ist ein für einen bestimmten Zweck konzipierter Zustand und immer ein Zustand der obersten Ebene. Wir verwenden ` m_stateCount `, um den Überblick darüber zu behalten, wie viele Zustände wir verwalten, und ` m_lastIndex `, um zu speichern, welcher Zustand der letzte war, in den wir gewechselt sind.

    void onEntry(QEvent *) override
    {
        int n;
        while ((n = QRandomGenerator::global()->bounded(m_stateCount) + 1) == m_lastIndex)
        { }
        m_lastIndex = n;
        machine()->postEvent(new StateSwitchEvent(n));
    }
    void onExit(QEvent *) override {}

Wir wählen den nächsten Zustand aus, in den wir wechseln wollen, und senden ein ` StateSwitchEvent`, von dem wir wissen, dass es den ` StateSwitchTransition ` zum ausgewählten Zustand auslöst.

    void addState(QState *state, QAbstractAnimation *animation) {
        auto trans = new StateSwitchTransition(++m_stateCount);
        trans->setTargetState(state);
        addTransition(trans);
        trans->addAnimation(animation);
    }

Hier geschieht das Wunder. Wir weisen jedem hinzugefügten Zustand eine Nummer zu. Diese Nummer wird sowohl einem `StateSwitchTransition` als auch den `StateSwitchEvents` zugewiesen. Wie wir gesehen haben, lösen Zustandswechselereignisse einen Übergang mit derselben Nummer aus.

Die Klasse `StateSwitchTransition`

StateSwitchTransition erbt von ` QAbstractTransition ` und wird bei ` StateSwitchEvent`s ausgelöst. Sie enthält ausschließlich Inline-Funktionen; schauen wir uns also ihre Funktion ` eventTest()` an, die einzige Funktion, die wir definieren.

    bool eventTest(QEvent *event) override
    {
        return (event->type() == QEvent::Type(StateSwitchEvent::StateSwitchType))
            && (static_cast<StateSwitchEvent *>(event)->rand() == m_rand);
    }

eventTest wird von ` QStateMachine ` aufgerufen, wenn geprüft wird, ob ein Übergang ausgelöst werden soll – ein Rückgabewert von `true` bedeutet, dass dies geschieht. Wir prüfen einfach, ob unsere zugewiesene Nummer mit der Nummer des Ereignisses übereinstimmt (in diesem Fall lösen wir den Übergang aus).

Die Klasse `StateSwitchEvent`

StateSwitchEvent erbt von ` QEvent` und enthält eine Nummer, die einem Zustand und einem Zustandswechsel durch ` StateSwitcher` zugewiesen wurde. Wir haben bereits in „ StateSwitcher “ gesehen, wie sie zum Auslösen von ` StateSwitchTransition`s verwendet wird.

class StateSwitchEvent: public QEvent
{
public:
    explicit StateSwitchEvent(int rand) : QEvent(StateSwitchType), m_rand(rand) { }

    static constexpr QEvent::Type StateSwitchType = QEvent::Type(QEvent::User + 256);

    int rand() const { return m_rand; }

private:
    int m_rand;
};

Da diese Klasse ausschließlich Inline-Funktionen enthält, reicht ein Blick auf ihre Definition aus.

Die Klasse `QGraphicsRectWidget`

QGraphicsRectWidget erbt von QGraphicsWidget und malt dessen rect() einfach blau aus. Wir verwenden paintEvent() als Inline-Funktion, was die einzige von uns definierte Funktion ist. Hier ist die Klassendefinition von QGraphicsRectWidget:

class QGraphicsRectWidget : public QGraphicsWidget
{
public:
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
    {
        painter->fillRect(rect(), Qt::blue);
    }
};

Weiter geht’s

Die in diesem Beispiel gezeigte Technik funktioniert für alle ` QPropertyAnimation` gleichermaßen gut. Solange der zu animierende Wert eine Qt-Eigenschaft ist, können Sie eine Animation davon in einen Zustandsgraphen einfügen.

QState::addAnimation() erwartet ein ` QAbstractAnimation`, sodass jede Art von Animation in den Graphen eingefügt werden kann.

Beispielprojekt @ code.qt.io

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.