Blöcke verschieben

Das Beispiel "Blöcke verschieben" zeigt, wie Elemente in einer QGraphicsScene mit Hilfe von QStateMachine mit einem benutzerdefinierten Übergang animiert werden können.

Das Beispiel animiert die blauen Blöcke, die Sie in der obigen Abbildung sehen können. Die Animation bewegt die Blöcke zwischen vier voreingestellten Positionen.

Das Beispiel besteht aus den folgenden Klassen:

  • StateSwitcher erbt QState und kann StateSwitchTransitionzu anderen Zuständen hinzufügen. Wenn es betreten wird, wird es zufällig in einen dieser Zustände übergehen.
  • StateSwitchTransition ist ein benutzerdefinierter Übergang, der bei StateSwitchEvents ausgelöst wird.
  • StateSwitchEvent ist eine QEvent, die StateSwitchTransitions auslöst.
  • QGraphicsRectWidget ist ein QGraphicsWidget, das seinen Hintergrund einfach in einer einfarbigen blue Farbe malt.

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 dann 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 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 QGraphicsView hinzugefügt wurde, ist es an der Zeit, den Zustandsgraphen zu erstellen. Schauen wir uns zunächst ein Zustandsdiagramm dessen an, was wir zu bauen versuchen.

Beachten Sie, dass group sieben Unterzustände hat, aber wir haben nur drei davon in das Diagramm aufgenommen. Der Code, mit dem dieses Diagramm erstellt wird, wird Zeile für Zeile untersucht, um zu zeigen, wie das Diagramm funktioniert. Als erstes wird der Zustand group erstellt:

    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 wird verwendet, um eine Verzögerung zwischen den einzelnen Bewegungen der Blöcke einzufügen. Der Timer wird gestartet, wenn group eingegeben wird. Wie wir später sehen werden, hat group einen Übergang zurück zu StateSwitcher, wenn der Timer abläuft. group ist der Anfangszustand in der Maschine, also wird eine Animation geplant, wenn das Beispiel gestartet 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 ein QState zurück, das die Geometrie unserer Elemente bei der Eingabe festlegt. Außerdem wird group als Elternteil dieses Zustands zugewiesen.

Ein QPropertyAnimation, das in einen Übergang eingefügt wird, verwendet die Werte, die einem QState (mit QState::assignProperty()) zugewiesen wurden, 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. Dies ist die 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, 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);

Ein StateSwitchTransition wird dem State Switcher in StateSwitcher::addState() hinzugefügt. In dieser Funktion fügen wir auch die Animation hinzu. Da QPropertyAnimation die Werte aus den Zuständen verwendet, können wir dieselbe QPropertyAnimation Instanz in alle StateSwitchTransitions einfügen.

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

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

Schließlich können wir den Zustandsautomaten 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 unter Verwendung der mit assignProperty() festgelegten Eigenschaftswerte ein.

Die StateSwitcher-Klasse

StateSwitcher verfügt über Zustandswechselübergänge zu jedem QStates, das wir mit createGeometryState() erstellt haben. Ihre Aufgabe ist es, zufällig in einen dieser Zustände zu wechseln, wenn er betreten wird.

Alle Funktionen in StateSwitcher sind inlined. Wir gehen schrittweise durch seine Definition.

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

StateSwitcher ist ein Zustand, der für einen bestimmten Zweck entwickelt wurde und immer ein Top-Level-Zustand sein wird. Wir verwenden m_stateCount, um zu verfolgen, wie viele Zustände wir verwalten, und m_lastIndex, um uns zu merken, welcher Zustand der letzte war, zu dem wir übergegangen 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 übergehen wollen, und senden einen StateSwitchEvent, von dem wir wissen, dass er den StateSwitchTransition in den ausgewählten Zustand auslösen wird.

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

An dieser Stelle geschieht die Magie. Wir weisen jedem hinzugefügten Zustand eine Nummer zu. Diese Nummer wird sowohl einem StateSwitchTransition als auch einem StateSwitchEvent zugewiesen. Wie wir gesehen haben, lösen StateSwitch-Ereignisse einen Übergang mit der gleichen Nummer aus.

Die StateSwitchTransition-Klasse

StateSwitchTransition erbt QAbstractTransition und löst StateSwitchEvents aus. Sie enthält nur Inline-Funktionen, also schauen wir uns ihre Funktion eventTest() an, die die einzige Funktion ist, 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 sie prüft, ob ein Übergang ausgelöst werden soll - ein Rückgabewert von true bedeutet, dass er ausgelöst wird. Wir prüfen einfach, ob die uns zugewiesene Nummer gleich der Nummer des Ereignisses ist (in diesem Fall lösen wir aus).

Die StateSwitchEvent-Klasse

StateSwitchEvent erbt QEvent und enthält eine Nummer, die einem Zustand und einem Zustandswechselübergang durch StateSwitcher zugewiesen wurde. Wir haben bereits gesehen, wie sie verwendet wird, um StateSwitchTransitions in StateSwitcher auszulösen.

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

Wir haben nur inlined Funktionen in dieser Klasse, also reicht ein Blick auf ihre Definition.

Die Klasse QGraphicsRectWidget

QGraphicsRectWidget erbt QGraphicsWidget und färbt seine rect() einfach blau. Wir inline paintEvent(), was die einzige Funktion ist, die wir definieren. Hier ist die Definition der Klasse QGraphicsRectWidget:

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

Weiter

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

QState::addAnimation() nimmt ein QAbstractAnimation, so dass jede Art von Animation in den Graphen eingefügt werden kann.

Beispielprojekt @ code.qt.io

© 2025 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.