Как выполнить обмен QGraphicsItem(s) в QGraphicsView?
Моя идея в этом проекте - выполнить анимацию свопинга на предметах. Проблема, однако, заключается в том, что когда я выполняю замену элементов в первый раз, они сохраняют свою позицию неподвижно, но когда начинается другая анимация, которая включает в себя уже замененные элементы, эти элементы возвращаются к своим начальным позициям. Пожалуйста, скажите мне, что я делаю не так. Анимация выглядит следующим образом:
#include <QtCore>
#include <QtWidgets>
/**
* Element to be displayed in QGraphicsView
*/
class QGraphicsRectWidget : public QGraphicsWidget
{
Q_OBJECT
int m_number;
public:
void changePosition(QGraphicsRectWidget *other)
{
setPos(mapToParent(other->x() < x() ? -abs(x() - other->x())
: abs(x() - other->x()) ,0));
}
static int NUMBER;
QGraphicsRectWidget(QGraphicsItem *parent = 0) : QGraphicsWidget(parent), m_number(NUMBER)
{ NUMBER++;}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *,
QWidget *) Q_DECL_OVERRIDE
{
painter->fillRect(rect(), QColor(127, 63, 63));
painter->drawText(rect(), QString("%1").arg(m_number), QTextOption(Qt::AlignCenter));
}
};
int QGraphicsRectWidget::NUMBER = 1;
class MyAnim : public QPropertyAnimation
{
Q_OBJECT
QGraphicsView &pview; // View in which elements must be swapped
int i1, i2; // Indices for elements to be swapped
public:
MyAnim(QGraphicsView &view, int index1 = 0, int index2 = 1, QObject *par = 0)
: QPropertyAnimation(par), pview(view), i1(index1), i2(index2)
{
QObject::connect(this, SIGNAL(finished()), SLOT(slotOnFinish()));
}
public slots:
/* !!!!!!!!!!!!!!!!!!!!!!! HERE IS THE PROBLEM (brobably) !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
// Triggered when animation is over and sets position of target element to position of its end value
void slotOnFinish()
{
auto list = pview.items();
static_cast<QGraphicsRectWidget*>(list.at(i1))
->changePosition(static_cast<QGraphicsRectWidget*>(list.at(i2)));
}
};
class GraphicsView : public QGraphicsView
{
Q_OBJECT
public:
GraphicsView(QGraphicsScene *scene, QWidget *parent = NULL) : QGraphicsView(scene, parent)
{
}
protected:
virtual void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE
{
fitInView(scene()->sceneRect());
QGraphicsView::resizeEvent(event);
}
};
#define SWAP_HEIGHT 75
/**
* Creates swap animation for items in QGraphicsView
*/
QParallelAnimationGroup* getSwapAnimation(QGraphicsView &view, int noItem1, int noItem2)
{
auto list = view.items();
QGraphicsRectWidget *wgt1 = static_cast<QGraphicsRectWidget*>(list.at(noItem1));
QGraphicsRectWidget *wgt2 = static_cast<QGraphicsRectWidget*>(list.at(noItem2));
MyAnim *pupperAnim, *plowerAnim;
QParallelAnimationGroup *par = new QParallelAnimationGroup;
plowerAnim = new MyAnim(view, noItem1, noItem2);
plowerAnim->setTargetObject(wgt2);
plowerAnim->setPropertyName("pos");
plowerAnim->setDuration(5000);
plowerAnim->setKeyValueAt(1.0/3.0, QPoint(wgt2->x(), wgt1->y() - SWAP_HEIGHT));
plowerAnim->setKeyValueAt(2.0/3.0, QPoint(wgt1->x(), wgt1->y() - SWAP_HEIGHT));
plowerAnim->setEndValue(wgt1->pos());
pupperAnim = new MyAnim(view, noItem2, noItem1);
pupperAnim->setTargetObject(wgt1);
pupperAnim->setPropertyName("pos");
pupperAnim->setDuration(5000);
pupperAnim->setKeyValueAt(1.0/3.0, QPoint(wgt1->x(), wgt2->y() + SWAP_HEIGHT));
pupperAnim->setKeyValueAt(2.0/3.0, QPoint(wgt2->x(), wgt2->y() + SWAP_HEIGHT));
pupperAnim->setEndValue(wgt2->pos());
par->addAnimation(pupperAnim);
par->addAnimation(plowerAnim);
return par;
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QGraphicsRectWidget *button1 = new QGraphicsRectWidget;
QGraphicsRectWidget *button2 = new QGraphicsRectWidget;
QGraphicsRectWidget *button3 = new QGraphicsRectWidget;
QGraphicsRectWidget *button4 = new QGraphicsRectWidget;
button2->setZValue(1);
button3->setZValue(2);
button4->setZValue(3);
QGraphicsScene scene(0, 0, 300, 300);
scene.setBackgroundBrush(QColor(23, 0, 0));
scene.addItem(button1);
scene.addItem(button2);
scene.addItem(button3);
scene.addItem(button4);
GraphicsView window(&scene);
window.setFrameStyle(0);
window.setAlignment(Qt::AlignLeft | Qt::AlignTop);
window.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
window.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
QList<QGraphicsItem*> items = window.items();
QPoint start(20, 125);
for (auto item : items) // Set items in initial position
{
QGraphicsWidget *wgt = static_cast<QGraphicsWidget*>(item);
wgt->resize(50,50);
wgt->moveBy(start.x(), start.y());
start.setX(start.x() + 70);
}
QSequentialAnimationGroup gr;
gr.addAnimation(getSwapAnimation(window, 0, 1));
gr.addAnimation(getSwapAnimation(window, 1, 2));
gr.addAnimation(getSwapAnimation(window, 2, 3));
gr.addAnimation(getSwapAnimation(window, 3, 1));
gr.start();
window.resize(300, 300);
window.show();
return app.exec();
}
#include "main.moc"
UPD: не используйте анимацию с этой целью
UPD*: забыть предыдущий UPD
1 ответ
Ваша анимация сохраняет положение элементов, задействованных во время создания анимации. Ко времени запуска второй анимации эта информация становится недействительной.
Вам нужно изменить дизайн анимации, чтобы обновить значения ключевых точек во время ее запуска. Вы также можете убедиться, что анимированные элементы работают с постоянной скоростью - или, по крайней мере, со скоростью, которую вы полностью контролируете.
Например:
// https://github.com/KubaO/stackrun/tree/master/questions/scene-anim-swap-40787655
#include <QtWidgets>
#include <cmath>
class QGraphicsRectWidget : public QGraphicsWidget
{
public:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
{
painter->fillRect(rect(), Qt::blue);
painter->setPen(Qt::yellow);
painter->drawText(rect(), QString::number(zValue()), QTextOption(Qt::AlignCenter));
}
};
class SwapAnimation : public QPropertyAnimation
{
QPointer<QObject> other;
qreal offset;
QPoint propertyOf(QObject *obj) {
return obj->property(propertyName().constData()).toPoint();
}
void updateState(State newState, State oldState) override {
if (newState == Running && oldState == Stopped) {
auto start = propertyOf(targetObject());
auto end = propertyOf(other);
auto step1 = fabs(offset);
auto step2 = QLineF(start,end).length();
auto steps = 2.0*step1 + step2;
setStartValue(start);
setKeyValueAt(step1/steps, QPoint(start.x(), start.y() + offset));
setKeyValueAt((step1+step2)/steps, QPoint(end.x(), end.y() + offset));
setEndValue(end);
setDuration(10.0 * steps);
}
QPropertyAnimation::updateState(newState, oldState);
}
public:
SwapAnimation(QObject *first, QObject *second, qreal offset) : other(second), offset(offset) {
setTargetObject(first);
setPropertyName("pos");
}
};
QParallelAnimationGroup* getSwapAnimation(QObject *obj1, QObject *obj2)
{
auto const swapHeight = 75.0;
auto par = new QParallelAnimationGroup;
par->addAnimation(new SwapAnimation(obj2, obj1, -swapHeight));
par->addAnimation(new SwapAnimation(obj1, obj2, swapHeight));
return par;
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QGraphicsScene scene(0, 0, 300, 300);
QGraphicsRectWidget buttons[4];
int i = 0;
QPointF start(20, 125);
for (auto & button : buttons) {
button.setZValue(i++);
button.resize(50,50);
button.setPos(start);
start.setX(start.x() + 70);
scene.addItem(&button);
}
QSequentialAnimationGroup gr;
gr.addAnimation(getSwapAnimation(&buttons[0], &buttons[1]));
gr.addAnimation(getSwapAnimation(&buttons[1], &buttons[2]));
gr.addAnimation(getSwapAnimation(&buttons[2], &buttons[3]));
gr.addAnimation(getSwapAnimation(&buttons[3], &buttons[1]));
gr.start();
QGraphicsView view(&scene);
view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view.resize(300, 300);
view.show();
return app.exec();
}