移动块
移动图块示例展示了如何使用带有自定义过渡的QStateMachine 为QGraphicsScene 中的项目制作动画。
该示例为上图中的蓝色图块设置了动画。动画在四个预设位置之间移动块。
示例由以下类组成:
StateSwitcher
继承 ,并可将 s 添加到其他状态。输入时,它会随机过渡到这些状态之一。QStateStateSwitchTransition
StateSwitchTransition
是一个自定义过渡,可在 s 时触发。StateSwitchEvent
StateSwitchEvent
是一个 ,可触发 s。QEventStateSwitchTransition
QGraphicsRectWidget
是一个 ,只需将其背景涂成 的纯色。QGraphicsWidget blue
这些区块是QGraphicsRectWidget
的实例,并在QGraphicsScene 中进行动画处理。我们通过构建状态图来实现这一点,并在其中插入动画。然后在QStateMachine 中执行该图。所有这些都在main()
中完成。让我们先看看main()
函数。
main()
函数
初始化QApplication 后,我们将QGraphicsScene 与QGraphicsRectWidget
s 设置在一起。
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);
将场景添加到QGraphicsView 后,就可以构建状态图了。让我们先看一下我们要构建的状态图。
请注意,group
有七个子状态,但我们在图中只包含了其中三个。我们将逐行检查构建该图的代码,并展示该图是如何工作的。首先,我们构建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));
定时器用于在每次块移动之间添加延迟。定时器在group
进入时启动。正如我们稍后将看到的,当定时器超时时,group
会过渡回StateSwitcher
。group
是机器的初始状态,因此在示例启动时将安排一个动画。
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()
返回的 将在进入时设置项目的几何图形。它还将 指定为该状态的父级。QState group
插入转换的QPropertyAnimation 将使用分配给QState 的值(使用QState::assignProperty()) ,也就是说,动画将在属性的当前值和目标状态的值之间进行插值。我们稍后会在状态图中添加动画转换。
QParallelAnimationGroup animationGroup; auto anim = new QPropertyAnimation(button4, "geometry"); anim->setDuration(1000); anim->setEasingCurve(QEasingCurve::OutElastic); animationGroup.addAnimation(anim);
我们并行地移动项目。每个项目都会被添加到animationGroup
中,这就是插入到转换中的动画。
auto subGroup = new QSequentialAnimationGroup(&animationGroup); subGroup->addPause(100); anim = new QPropertyAnimation(button3, "geometry"); anim->setDuration(1000); anim->setEasingCurve(QEasingCurve::OutElastic); subGroup->addAnimation(anim);
顺序动画组subGroup
帮助我们在每个项目的动画之间插入延迟。
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);
在StateSwitcher::addState()
中的状态切换器中添加了一个 StateSwitchTransition。我们还在该函数中添加了动画。由于QPropertyAnimation 使用的是状态值,因此我们可以在所有StateSwitchTransition
中插入相同的QPropertyAnimation 实例。
如前所述,我们在状态切换器中添加了一个过渡,当定时器超时时触发。
machine.addState(group); machine.setInitialState(group); machine.start();
最后,我们可以创建状态机,添加初始状态,并开始执行状态图。
createGeometryState()
函数
在createGeometryState()
中,我们为每个图形项目设置几何图形。
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; }
如前所述,QAbstractTransition 将使用assignProperty() 中设置的属性值设置通过addAnimation() 添加的动画。
状态切换器类
StateSwitcher
QState createGeometryState()
它的任务是在进入状态时随机过渡到其中一种状态。
StateSwitcher
中的所有函数都是内联的。我们将逐步介绍它的定义。
class StateSwitcher : public QState { Q_OBJECT public: explicit StateSwitcher(QStateMachine *machine) : QState(machine) { }
StateSwitcher
是一个为特定目的而设计的状态,并且始终是一个顶级状态。我们使用 来记录我们正在管理多少个状态,使用 来记住我们过渡到的最后一个状态。m_stateCount
m_lastIndex
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 {}
我们选择下一个要过渡到的状态,并发布StateSwitchEvent
,我们知道这将触发StateSwitchTransition
到所选状态。
void addState(QState *state, QAbstractAnimation *animation) { auto trans = new StateSwitchTransition(++m_stateCount); trans->setTargetState(state); addTransition(trans); trans->addAnimation(animation); }
这就是神奇的地方。我们为添加的每个状态分配一个编号。这个编号既赋予状态切换转换(StateSwitchTransition),也赋予状态切换事件(StateSwitchEvents)。正如我们所看到的,状态切换事件将触发具有相同编号的转换。
状态切换转换类
StateSwitchTransition
继承于 并触发 s。它只包含内联函数,所以我们来看看它的 () 函数,这也是我们定义的唯一一个函数。QAbstractTransition StateSwitchEvent
eventTest
bool eventTest(QEvent *event) override { return (event->type() == QEvent::Type(StateSwitchEvent::StateSwitchType)) && (static_cast<StateSwitchEvent *>(event)->rand() == m_rand); }
eventTest
QStateMachine 在检查是否应触发转换时会调用该函数,返回值为 true 表示应触发转换。我们只需检查我们分配的数字是否等于事件的数字(在这种情况下,我们就会触发)。
状态切换事件类
StateSwitchEvent
继承于 ,并保存一个由 分配给状态和状态切换转换的编号。我们已经在 中看到它是如何用于触发 s 的。QEvent StateSwitcher
StateSwitcher
StateSwitchTransition
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; };
我们在该类中只有内联函数,因此只需查看其定义即可。
QGraphicsRectWidget 类
QGraphicsRectWidget 继承了QGraphicsWidget ,并简单地将其rect() 涂成蓝色。我们内联了paintEvent() 函数,这也是我们定义的唯一一个函数。下面是 QGraphicsRectWidget 类的定义:
class QGraphicsRectWidget : public QGraphicsWidget { public: void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override { painter->fillRect(rect(), Qt::blue); } };
继续
QPropertyAnimation只要要动画的值是 Qt 属性,您就可以在状态图中插入它的动画。
QState::addAnimation() 接收一个QAbstractAnimation ,因此任何类型的动画都可以插入到状态图中。
© 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.