Search code examples
pythonpyqtpyqt5qvariantanimation

Why does setDuration not work inside QSequentialAnimationGroup?


I have two QVariantAnimation stuffed into a QSequentialAnimationGroup and I'm trying to invert one of them. I tried to do it like this

self.animation_1 = QtCore.QVariantAnimation()
self.animation_2 = QtCore.QVariantAnimation()

self.animation_1.setDirection(QtCore.QVariantAnimation.Backward)
self.animation_2.setDirection(QtCore.QVariantAnimation.Forward)

self.group = QtCore.QSequentialAnimationGroup(self)
self.group.addAnimation(self.animation_1)
self.group.addAnimation(self.animation_2)

But it didn’t work. I tried so

self.group.setDirection(QtCore.QVariantAnimation.Backward)

But the whole animation is inverted. and I only need to invert one of them. I tried to do this by referring to the index

self.group.animationAt(0).setDirection(QtCore.QVariantAnimation.Backward)

Zero reactions. I also tried to do it like this

self.group.animationAt(self.group.indexOfAnimation(animation_1)).setDirection(QtCore.QVariantAnimation.Backward)

It still didn't help.

How to do it?

My code

import sys

from PyQt5 import QtCore, QtGui, QtWidgets


class Main(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        block = QtWidgets.QWidget(self)
        block.setObjectName(u"block")
        block.setMinimumSize(QtCore.QSize(300, 300))
        self.block_but = QtWidgets.QWidget(block)
        self.block_but.setObjectName(u"block_but")
        self.block_but.setGeometry(QtCore.QRect(110, 20, 111, 111))
        move_blur_button_lay = QtWidgets.QVBoxLayout(self.block_but)
        move_blur_button_lay.setObjectName(u"move_blur_button_lay")
        self.menu_arr = []

        radio = QtWidgets.QPushButton(self.block_but)
        radio.setText('click me')
        radio.setObjectName("mv_radio_but")
        radio.pressed.connect(self.menu_animation)
        move_blur_button_lay.addWidget(radio)
        for x in range(2):
            menu = QtWidgets.QWidget(block)
            menu.resize(50, 60)
            menu._expand = False
            self.menu_arr.append(menu)
        self.menu_arr[0].setStyleSheet("""background:#ff0""")

        self.animation = []
        for i in range(2):
            menuAnimation = QtCore.QVariantAnimation()
            menuAnimation.setDuration(500)
            menuAnimation.setEasingCurve(QtCore.QEasingCurve.OutQuart)
            menuAnimation.setStartValue(QtCore.QPoint(-20, 10))
            menuAnimation.setEndValue(QtCore.QPoint(0, 10))
            self.animation.append(menuAnimation)
        self.group = QtCore.QSequentialAnimationGroup(self)

    def menu_animation(self):
        for c in range(2):
            pos1 = QtCore.QPoint(0, 0)
            pos2 = QtCore.QPoint(100, 0)
            self.animation[c].setStartValue(pos1)
            self.animation[c].setEndValue(pos2)
            self.animation[c].setDirection(QtCore.QAbstractAnimation.Backward)
            self.animation[c].valueChanged.connect(
                lambda value, val=c: self.fun(value, val)
            )
        #self.animation[0].setDirection(QtCore.QVariantAnimation.Backward)
        #self.animation[1].setDirection(QtCore.QVariantAnimation.Forward)

        self.animation[0].setStartValue(pos1) 
        self.animation[0].setEndValue(pos2) 
        self.animation[1].setStartValue(pos2) 
        self.animation[1].setEndValue(pos1)




        self.group.addAnimation(self.animation[0])
        self.group.addAnimation(self.animation[1])
        self.group.start()

    def fun(self, value, val=""):
        self.menu_arr[val].move(value.x(), 0)


StyleSheet = """
QWidget QWidget,
QWidget QWidget QWidget QWidget,
QWidget QWidget QWidget QWidget QWidget QWidget,
QWidget QWidget QWidget QWidget QWidget QWidget QWidget QWidget,
QWidget QWidget QWidget QWidget QWidget QWidget QWidget QWidget QWidget QWidget{
background:#000;
color:#fff;
}
QWidget,
QWidget QWidget QWidget,
QWidget QWidget QWidget QWidget QWidget,
QWidget QWidget QWidget QWidget QWidget QWidget QWidget,
QWidget QWidget QWidget QWidget QWidget QWidget QWidget QWidget QWidget{
background:#fff;
color:#000;
}
"""

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    app.setStyleSheet(StyleSheet)
    w = Main()
    w.resize(640, 570)  
    w.show()
    sys.exit(app.exec_())

What I want to get

enter image description here


Solution

  • It seems that it is not documented how the direction of QSequentialAnimationGroup affects the directions of the animations, but if the source code is reviewed:

    void QSequentialAnimationGroup::updateDirection(QAbstractAnimation::Direction direction)
    {
        Q_D(QSequentialAnimationGroup);
        // we need to update the direction of the current animation
        if (state() != Stopped && d->currentAnimation)
            d->currentAnimation->setDirection(direction);
    }
    

    It is noted that the address of the QSequentialAnimationGroup is applied to the animations. The solution would be to override that method and override that behavior but unfortunately that method cannot be accessed from python.

    A workaround would be to implement the logic by connecting the clicked signal to the start() method of the first animation and the finished signal of the first animation to the start() method of the second animation.

    class Main(QtWidgets.QWidget):
        def __init__(self):
            super().__init__()
            block = QtWidgets.QWidget(self)
            block.setMinimumSize(300, 300)
            self.block_but = QtWidgets.QWidget(block)
            self.block_but.setGeometry(QtCore.QRect(110, 20, 111, 111))
    
            radio = QtWidgets.QPushButton("click me", self.block_but)
            move_blur_button_lay = QtWidgets.QVBoxLayout(self.block_but)
            move_blur_button_lay.addWidget(radio)
    
            first_widget = QtWidgets.QWidget(block)
            first_widget.resize(50, 60)
            first_widget.setStyleSheet("""background:#ff0""")
    
            second_widget = QtWidgets.QWidget(block)
            second_widget.resize(50, 60)
    
            pos1 = QtCore.QPoint(0, 0)
            pos2 = QtCore.QPoint(100, 0)
    
            self.first_animation = QtCore.QVariantAnimation(
                self,
                duration=500,
                easingCurve=QtCore.QEasingCurve.OutQuart,
                startValue=pos1,
                endValue=pos2,
                direction=QtCore.QVariantAnimation.Forward,
                valueChanged=first_widget.move,
            )
    
            self.second_animation = QtCore.QVariantAnimation(
                self,
                duration=500,
                easingCurve=QtCore.QEasingCurve.OutQuart,
                startValue=pos1,
                endValue=pos2,
                direction=QtCore.QVariantAnimation.Backward,
                valueChanged=second_widget.move,
            )
    
            radio.clicked.connect(self.first_animation.start)
            self.first_animation.finished.connect(self.second_animation.start)