移动方块
“移动方块”示例演示了如何通过QStateMachine 配合自定义过渡效果,在QGraphicsScene 中实现元素的动画效果。

该示例对上图中可见的蓝色方块进行了动画处理。动画使方块在四个预设位置之间移动。
该示例包含以下类:
StateSwitcher继承自QState ,并可向其他状态添加StateSwitchTransition。进入该状态时,将随机过渡到其中一个状态。StateSwitchTransition是一种自定义过渡效果,在StateSwitchEvent发生时触发。StateSwitchEvent是一个QEvent ,用于触发StateSwitchTransition。QGraphicsRectWidget这是一个QGraphicsWidget ,仅将其背景填充为纯色blue 。
这些积木是QGraphicsRectWidget 的实例,并在QGraphicsScene 中进行动画播放。我们通过构建状态图来实现这一点,并将动画插入其中。随后,该状态图在QStateMachine 中执行。所有这些操作都在main() 中完成。让我们先看看main() 函数。
main() 函数
在初始化QApplication 之后,我们会通过其QGraphicsRectWidget来配置QGraphicsScene 。
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 类
StateSwitcher 包含指向我们通过createGeometryState() 创建的各个QState的状态切换过渡。其作用是在进入时随机过渡到其中一个状态。
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 类
StateSwitchTransition 继承自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 类
StateSwitchEvent 继承自 `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 作为参数,因此任何类型的动画都可以插入到状态图中。
© 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.