如何在 QGraphicsView 中执行 QGraphicsItem(s) 的交换?

How to perform swap of QGraphicsItem(s) in 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

您的动画在创建动画时保留所涉及项目的位置。到第二个动画 运行s 时,此信息已失效。

您需要重新设计动画以在开始时更新其关键点值。您可能还希望确保动画项目 运行 以恒定速度 - 或者至少以您可以完全控制的速度。

例如:

// https://github.com/KubaO/Whosebugn/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();
}