Fading out multiple objects in pyQT

Viewed 28

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()
2 Answers

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()

Tweaked for PyQT6 and added a function to get rid of the row once it's faded out.

from PyQt6.QtCore import *
from PyQt6.QtWidgets import *

class RowFadeController(QObject):
    def __init__(self, parent, buttons):
        super().__init__(parent)
        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)
        self.animation.finished.connect(self.hideRow)

    def toggle(self, hide):
        self.animation.setDirection(
            self.animation.Direction.Forward if hide else self.animation.Direction.Backward
        )
        self.animation.start()

    def setOpacity(self, opacity):
        for effect in self.effects:
            effect.setOpacity(opacity)

    def hideRow(self):
        for button in self.buttons:
            button.hide()
            button.setParent(None)


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)
            rowButtons.append(toggleButton)
            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()
Related