PyQt 中 QGridLayout 中的动画几何

Animate Geometry inside QGridLayout in PyQt

如何为 QGridLayout 中的几何变化设置动画。我有 QLabel,它将放置在 QGridlayout 中。当鼠标在 QLabel 内部时它应该展开,在外部时收缩回正常状态。我设法制作了动画,但它并没有从所有四个方面展开。它反而远离网格。

MRE:

import sys
from PyQt5 import QtWidgets, QtCore


class Tile(QtWidgets.QLabel):

    def __init__(self, *args, **kwargs):
        super(Tile, self).__init__(*args, **kwargs)

        # p = self.palette()
        # p.setColor(self.backgroundRole(), QtCore.Qt.red)
        # self.setPalette(p)
        self.setText("hello")
        self.setMinimumSize(100, 100)
        self.setMaximumSize(125, 125)

    def enterEvent(self, a0: QtCore.QEvent) -> None:
        super(Tile, self).enterEvent(a0)

        self.animation = QtCore.QPropertyAnimation(self, b"geometry")
        self.animation.setStartValue(QtCore.QRect(self.geometry()))
        self.animation.setEndValue(QtCore.QRect(self.geometry().adjusted(-25, -25, 25, 25)))
        self.animation.setDuration(150)
        self.animation.start(QtCore.QPropertyAnimation.DeleteWhenStopped)

    def leaveEvent(self, a0: QtCore.QEvent) -> None:
        super(Tile, self).leaveEvent(a0)

        self.animation = QtCore.QPropertyAnimation(self, b"geometry")
        self.animation.setStartValue(QtCore.QRect(self.geometry()))
        self.animation.setEndValue(QtCore.QRect(self.geometry().adjusted(25, 25, -25, -25)))
        self.animation.setDuration(150)
        self.animation.start(QtCore.QPropertyAnimation.DeleteWhenStopped)


class ScrollView(QtWidgets.QWidget):

    def __init__(self, *args, **kwargs):
        super(ScrollView, self).__init__(*args, **kwargs)

        self.setStyleSheet('border: 1px solid black')

        self.setLayout(QtWidgets.QVBoxLayout())

        widget = QtWidgets.QWidget()

        self.grid_layout = QtWidgets.QGridLayout(widget)

        self.scrollArea = QtWidgets.QScrollArea()
        self.scrollArea.setWidget(widget)
        self.scrollArea.setWidgetResizable(True)

        self.grid_layout.setSpacing(50)

        self.row_width = 4

        self._row = 0
        self._column = 0

        self.layout().addWidget(self.scrollArea)

    def addTile(self):

        self.grid_layout.addWidget(Tile(), self._row, self._column)

        if self._column == 3:
            self._row += 1
            self._column = 0

        else:
            self._column += 1


def main():
    app = QtWidgets.QApplication(sys.argv)

    win = ScrollView()

    for x in range(30):
        win.addTile()

    win.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()

主要问题是您设置的最大尺寸比最终值:如果您有一个尺寸为 100x100 的起始矩形并将其扩展 25 像素在每一侧,它将变为 150x150,而不是 125x125。由于您已经设置了最大值,一旦几何体达到 125x125,它只会更改坐标,同时保持最大尺寸。

但是还有另外三个问题。

  1. 您总是使用 current 几何作为起始值,这可能会成为一个问题:如果您进入或离开 while其他动画正在进行中,您得到了错误的目标几何体参考;
  2. 如果你 enter/leave 小部件非常快,你最终会同时发生两个动画;每次都连续创建动画实际上没有任何好处;
  3. 小部件未考虑调整滚动区域的大小,这会改变纵横比并为新定位带来问题;

为了避免这一切,你必须只使用一个动画,根据enter/leave事件改变它的方向,适当修改start/end 值根据实际几何形状变化,并在发生 external 调整大小时适当调整为“默认”大小;最后两点仅完成 if 动画 not 活动(因为 moveEvent 和 resizeEvent 由几何变化调用)。

class Tile(QtWidgets.QLabel):

    def __init__(self, *args, **kwargs):
        super(Tile, self).__init__(*args, **kwargs)

        self.setText("hello")
        self.setMinimumSize(100, 100)
        self.setMaximumSize(150, 150)
        self.animation = QtCore.QPropertyAnimation(self, b"geometry")
        self.animation.setDuration(150)

    def animate(self, expand):
        if expand:
            self.animation.setDirection(self.animation.Forward)
        else:
            self.animation.setDirection(self.animation.Backward)
        self.animation.start()

    def enterEvent(self, a0: QtCore.QEvent) -> None:
        super(Tile, self).enterEvent(a0)
        self.animate(True)

    def leaveEvent(self, a0: QtCore.QEvent) -> None:
        super(Tile, self).leaveEvent(a0)
        self.animate(False)

    def updateAnimation(self):
        if not self.animation.state():
            center = self.geometry().center()
            start = QtCore.QRect(QtCore.QPoint(), self.minimumSize())
            start.moveCenter(center)
            self.animation.setStartValue(start)
            end = QtCore.QRect(QtCore.QPoint(), self.maximumSize())
            end.moveCenter(center)
            self.animation.setEndValue(end)

    def moveEvent(self, event):
        self.updateAnimation()

    def resizeEvent(self, event):
        self.updateAnimation()
        if not self.animation.state():
            rect = QtCore.QRect(QtCore.QPoint(), 
                self.maximumSize() if self.underMouse() else self.minimumSize())
            rect.moveCenter(self.geometry().center())
            self.setGeometry(rect)