移动方块
移动方块示例演示了如何使用自定义转换的 QGraphicsScene 和 QStateMachine 来动画化项目。
该示例动画化了您在上图中可以看到的蓝色方块。动画会将方块移动到四个预设位置之间。
该示例包含以下类
StateSwitcher
继承自 QState 并且可以将StateSwitchTransition
添加到其他状态中。当进入时,它将随机转换到这些状态之一。StateSwitchTransition
是一种在StateSwitchEvent
触发时触发的自定义转换。StateSwitchEvent
是一种触发StateSwitchTransition
的 QEvent。QGraphicsRectWidget
是一个 QGraphicsWidget,它仅仅用纯蓝色填充其背景。
方块是 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使用状态中的值,我们可以将相同的QPropertyAnimation实例插入所有StateSwitchTransition
中。
如前所述,我们添加了一个在计时器超时时触发的转换到状态切换器中。
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,因此可以将任何类型的动画插入到图中。
© 2024 The Qt Company Ltd. 本文档的贡献者对其各自的贡献享有版权。提供的文档根据 Free Software Foundation 发布的 GNU Free Documentation License 版本 1.3 的条款授权。Qt 及其相关标志是 The Qt Company Ltd. 在芬兰和/或其他国家的商标。所有其他商标均属于其各自的所有者。