如何在 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 中移动项目

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

如何仅执行 qgraphicsitem 的 mouseevent ? (忽略 qgraphicsview 的其余 mouseevent)

如何在 QGraphicsView::Scale 之后调整 QGraphicsView 的大小

如何在 QT 中的 QgraphicsView 中保持图像原始

如何在 QGraphicsView 中自动正确设置矩形?