Search code examples
pyqtpyqt5pyqt6

Fading out multiple objects in pyQT


I'm trying to fade out an entire row of a GridLayout when a user clicks a button. So far I've been able to loop each of the widgets on the specific row which is working but I'm struggling with creating a parallel animation for them all...

    idx = self.parent().findChild(QGridLayout).indexOf(self)
    location = self.parent().findChild(QGridLayout).getItemPosition(idx)
    row, col = location[:2]
    self.parent_grid = self.parent().findChild(QGridLayout)
    col_count = self.parent().findChild(QGridLayout).columnCount()
    print(col_count)
    self.effects = []
    self.animations = []
    anim_group = QParallelAnimationGroup()
    for i in range(col_count-1):
        effect = QGraphicsOpacityEffect(self)
        self.effects.append(effect)
        self.parent_grid.itemAtPosition(row,i).widget().setGraphicsEffect(effect)
        new_anim = QPropertyAnimation(effect, b"opacity")
        new_anim.setDuration(1000)
        new_anim.setStartValue(1)
        new_anim.setEndValue(0)
        new_anim.finished.connect(lambda: self.hide_widget(col_count, row))
        self.animations.append(new_anim)
        anim_group.addAnimation(new_anim)

    anim_group.start()

Solution

  • Since the count of widgets might be high, it's not a good idea to create an animation for each one of them.

    Instead, you can use a "controller" that has its own unique animation, and updates the opacity of each widgets in its row.

    While using a QPropertyAnimation can work, it would require adding a custom property for that, and it's not really needed for this purpose. Instead, use a basic QVariantAnimation and connect its valueChanged to a function that updates the opacity of each graphics effect.

    Here is a possible implementation:

    from PyQt5.QtCore import *
    from PyQt5.QtWidgets import *
    
    class RowFadeController(QObject):
        def __init__(self, parent, buttons):
            super().__init__()
            self.buttons = buttons
            self.effects = []
            for button in buttons:
                effect = QGraphicsOpacityEffect(button, opacity=1.0)
                button.setGraphicsEffect(effect)
                self.effects.append(effect)
    
            self.animation = QVariantAnimation(self)
            self.animation.setStartValue(1.)
            self.animation.setEndValue(0.)
            self.animation.valueChanged.connect(self.setOpacity)
    
        def toggle(self, hide):
            self.animation.setDirection(
                self.animation.Forward if hide else self.animation.Backward
            )
            self.animation.start()
    
        def setOpacity(self, opacity):
            for effect in self.effects:
                effect.setOpacity(opacity)
    
    
    class Window(QWidget):
        def __init__(self):
            super().__init__()
            layout = QGridLayout(self)
            self.fadeControllers = []
            for row in range(10):
                rowButtons = []
                for col in range(10):
                    button = QPushButton(str(col + 1))
                    layout.addWidget(button, row, col)
                    rowButtons.append(button)
    
                toggleButton = QPushButton('toggle', checkable=True)
                layout.addWidget(toggleButton, row, layout.columnCount() - 1)
    
                fadeController = RowFadeController(self, rowButtons)
                self.fadeControllers.append(fadeController)
                toggleButton.clicked.connect(fadeController.toggle)
    
    app = QApplication([])
    test = Window()
    test.show()
    app.exec()