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 kannStateSwitchTransition
zu 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 beiStateSwitchEvent
s ausgelöst wird.StateSwitchEvent
ist eine QEvent, dieStateSwitchTransition
s 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 QGraphicsRectWidget
s 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 StateSwitchTransition
s 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 StateSwitchEvent
s 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 StateSwitchTransition
s 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.
© 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.