Sur cette page

Déplacer des blocs

L'exemple « Déplacer des blocs » montre comment animer des éléments dans une QGraphicsScene à l'aide d'un QStateMachine avec une transition personnalisée.

Quatre carrés bleus sur fond noir

L'exemple anime les blocs bleus que vous pouvez voir sur l'image ci-dessus. L'animation déplace les blocs entre quatre positions prédéfinies.

L'exemple se compose des classes suivantes :

  • StateSwitcher hérite de ` QState ` et permet d’ajouter des ` StateSwitchTransition` à d’autres états. Lorsqu’on y accède, il effectue une transition aléatoire vers l’un de ces états.
  • StateSwitchTransition est une transition personnalisée qui se déclenche lors d’un « StateSwitchEvent».
  • StateSwitchEvent est un « QEvent » qui déclenche des « StateSwitchTransitions ».
  • QGraphicsRectWidget est une « QGraphicsWidget » qui se contente de peindre son arrière-plan d’une couleur unie « blue ».

Les blocs sont des instances de ` QGraphicsRectWidget ` et sont animés dans un ` QGraphicsScene`. Pour ce faire, nous construisons un graphe d’états dans lequel nous insérons des animations. Le graphe est ensuite exécuté dans un ` QStateMachine`. Tout cela se fait dans ` main()`. Examinons d’abord la fonction ` main() `.

La fonction main()

Une fois QApplication initialisé, nous configurons l’ QGraphicsScene avec ses QGraphicsRectWidgets.

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

Une fois la scène ajoutée à un QGraphicsView, il est temps de construire le graphe d’états. Examinons d’abord un diagramme d’états représentant ce que nous essayons de construire.

StateSwitcher gérant les transitions dans « Group State » avec State1, State2 et State3

Notez que l’état « group » comporte sept sous-états, mais nous n’en avons inclus que trois dans le diagramme. Le code qui construit ce graphe sera examiné ligne par ligne et montrera comment le graphe fonctionne. Tout d’abord, nous construisons l’état « 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));

Le temporisateur sert à ajouter un délai entre chaque déplacement des blocs. Il est déclenché lors de l’entrée dans l’état « group ». Comme nous le verrons plus tard, l’état « group » comporte une transition de retour vers l’état « StateSwitcher » lorsque le temporisateur arrive à expiration. L’état « group » est l’état initial de la machine ; une animation sera donc planifiée au démarrage de l’exemple.

    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() renvoie un « QState » qui définira la géométrie de nos éléments à l’entrée. Il attribue également à « group » le rôle de parent de cet état.

Une animation « QPropertyAnimation » insérée dans une transition utilisera les valeurs attribuées à un « QState » (via QState::assignProperty()), c’est-à-dire que l’animation effectuera une interpolation entre les valeurs actuelles des propriétés et celles de l’état cible. Nous ajouterons plus tard des transitions animées au graphe d’états.

    QParallelAnimationGroup animationGroup;

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

Nous déplaçons les éléments en parallèle. Chaque élément est ajouté à ` animationGroup`, qui est l’animation insérée dans les transitions.

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

Le groupe d’animations séquentielles, subGroup, nous permet d’insérer un délai entre l’animation de chaque élément.

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

Une instance `StateSwitchTransition` est ajoutée au sélecteur d’états dans ` StateSwitcher::addState()`. Nous ajoutons également l’animation dans cette fonction. Étant donné que ` QPropertyAnimation ` utilise les valeurs des états, nous pouvons insérer la même instance ` QPropertyAnimation ` dans tous les ` StateSwitchTransition`.

Comme mentionné précédemment, nous ajoutons une transition au sélecteur d’états qui se déclenche lorsque le temporisateur arrive à expiration.

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

Enfin, nous pouvons créer la machine à états, ajouter notre état initial et lancer l’exécution du graphe d’états.

La fonction « createGeometryState() »

Dans ` createGeometryState()`, nous configurons la géométrie de chaque élément graphique.

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

Comme indiqué précédemment, QAbstractTransition configurera une animation ajoutée via addAnimation() à l’aide des valeurs de propriété définies par assignProperty().

La classe StateSwitcher

StateSwitcher dispose de transitions de changement d’état vers chacun des objets ` QState` que nous avons créés avec ` createGeometryState()`. Son rôle est d’effectuer une transition vers l’un de ces états de manière aléatoire lorsqu’on y accède.

Toutes les fonctions de StateSwitcher sont intégrées. Nous allons passer en revue sa définition.

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

StateSwitcher est un état conçu pour un usage particulier et sera toujours un état de niveau supérieur. Nous utilisons ` m_stateCount ` pour compter le nombre d’états que nous gérons, et ` m_lastIndex ` pour mémoriser le dernier état vers lequel nous avons effectué une transition.

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

Nous sélectionnons l’état suivant vers lequel nous allons effectuer la transition, puis nous envoyons un ` StateSwitchEvent`, sachant que cela déclenchera la méthode ` StateSwitchTransition ` vers l’état sélectionné.

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

C’est là que la magie opère. Nous attribuons un numéro à chaque état ajouté. Ce numéro est transmis à la fois à StateSwitchTransition et à StateSwitchEvents. Comme nous l’avons vu, les événements de changement d’état déclencheront une transition portant le même numéro.

La classe StateSwitchTransition

StateSwitchTransition hérite de ` QAbstractTransition ` et se déclenche lors d’ StateSwitchEvents. Elle ne contient que des fonctions inline ; examinons donc sa fonction ` eventTest()`, qui est la seule que nous définissons.

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

eventTest est appelée par ` QStateMachine ` lorsqu’elle vérifie si une transition doit être déclenchée : une valeur de retour égale à `true` signifie qu’elle le sera. Nous vérifions simplement si le numéro que nous avons attribué est égal au numéro de l’événement (auquel cas nous déclenchons la transition).

La classe `StateSwitchEvent`

StateSwitchEvent hérite de ` QEvent` et contient un numéro attribué à un état et à une transition de changement d’état par ` StateSwitcher`. Nous avons déjà vu comment elle est utilisée pour déclencher des ` StateSwitchTransition` dans ` StateSwitcher`.

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

Cette classe ne contient que des fonctions intégrées ; il suffit donc d’examiner sa définition.

La classe QGraphicsRectWidget

QGraphicsRectWidget hérite de QGraphicsWidget et se contente de peindre son rect() en bleu. Nous intégrons paintEvent(), qui est la seule fonction que nous définissons. Voici la définition de la classe QGraphicsRectWidget :

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

Allons plus loin

La technique présentée dans cet exemple fonctionne de la même manière pour tous les objets ` QPropertyAnimation`. Tant que la valeur à animer est une propriété Qt, vous pouvez insérer une animation de celle-ci dans un graphe d’états.

La fonction QState::addAnimation() prend en paramètre un objet ` QAbstractAnimation`, ce qui permet d’insérer n’importe quel type d’animation dans le graphe.

Exemple de projet sur 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.