如何在 QGraphicsView 中执行 QGraphicsItem(s) 的交换?
Posted
技术标签:
【中文标题】如何在 QGraphicsView 中执行 QGraphicsItem(s) 的交换?【英文标题】:How to perform swap of QGraphicsItem(s) in QGraphicsView? 【发布时间】:2016-11-24 13:27:41 【问题描述】:我在这个项目中的想法是在项目上执行交换动画。 然而问题是,当我第一次对项目执行交换时,它们保持其位置不变,但是当另一个涉及已交换项目的动画开始时,这些项目会回落到它们的初始位置。请告诉我我做错了什么。动画如下:
#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
【问题讨论】:
“UPD:不要为此目的使用动画”当然你可以也应该使用动画,只要做对:) @KubaOber 是的,但考虑到我与“Qt 5.3 Professional Programming with C++”一书的作者 Max Schlee 交谈过,并被告知不要使用它,我决定不使用它。我并不是说你不能用动画来做到这一点,但这对臀部来说是一个巨大的痛苦。而且我已经找到了解决办法 使用有效的方法和产生最易读的代码。我怀疑除非您为很多项目设置动画,否则性能将发挥很大作用。如果你这样做了,那么你可以从QAbstractAnimation
开始并实现QGraphicsItemPositionAnimation
,这将避免拥有QObject
项目等的开销。:)
@KubaOber 不能不同意!感谢您的努力:D
【参考方案1】:
您的动画会保留所涉及项目的位置在创建动画时。到第二个动画运行时,此信息已无效。
您需要重新设计动画以在动画开始时更新其关键点值。您可能还希望确保动画项目以恒定速度运行 - 或至少以您可以完全控制的速度运行。
例如:
// https://github.com/KubaO/***n/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();
【讨论】:
非常感谢您的解决方案!不过,我已经找到了一个不使用动画的。希望大家觉得有用!以上是关于如何在 QGraphicsView 中执行 QGraphicsItem(s) 的交换?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 QGraphicsView 中执行 QGraphicsItem(s) 的交换?
如何仅执行 qgraphicsitem 的 mouseevent ? (忽略 qgraphicsview 的其余 mouseevent)
如何在 QGraphicsView::Scale 之后调整 QGraphicsView 的大小