为 QPushbutton enterEvent 和 exitEvent 添加动画

Adding animation to QPushbutton enterEvent and exitEvent

我正在尝试向 QPushbutton 添加自定义动画,而无需制作自定义 QPushbutton 并覆盖其 enterEvent() 和 leaveEvent()。

到目前为止我已经试过了,

@staticmethod
def addButtonHoverAnimation(button:QPushButton,currentPos:QPoint):
    '''
    Method to:
    => Add hover animation for provided button
    '''
    enterShift = QPropertyAnimation(button,b'pos',button)
    exitShift = QPropertyAnimation(button,b'pos',button)

    def enterEvent(e):
        pos=button.pos()            
        enterShift.setStartValue(pos)
        enterShift.setEndValue(QPoint(pos.x()+3,pos.y()+3))
        enterShift.setDuration(100)
        enterShift.start()
        Effects.dropShadow(button,1,2)
        

    def leaveEvent(e):
        pos=button.pos()            
        exitShift.setStartValue(pos)
        exitShift.setEndValue(QPoint(pos.x()-3,pos.y()-3))
        exitShift.setDuration(100)
        exitShift.start()
        Effects.dropShadow(button)
    

    button.enterEvent=enterEvent
    button.leaveEvent=leaveEvent

但是当我在动画结束前快速将鼠标移入和移出按钮时,按钮开始奇怪地向西北方向移动。

使用动态位置的按钮动画

我发现这是由于 leaveEvent() 在 enterEvent() 完成之前被触发,而且因为开始和结束值是动态的。因此,我尝试提供 currentPos 作为静态位置并使用它,

@staticmethod
def addButtonHoverAnimation(button:QPushButton,currentPos:QPoint):
    '''
    Method to:
    => Add hover animation for provided button
    '''
    enterShift = QPropertyAnimation(button,b'pos',button)
    enterShift.setStartValue(currentPos)
    enterShift.setEndValue(QPoint(currentPos.x()+3,currentPos.y()+3))
    enterShift.setDuration(100) 
    exitShift = QPropertyAnimation(button,b'pos',button)
    exitShift.setStartValue(QPoint(currentPos.x()-3,currentPos.y()-3))
    exitShift.setEndValue(currentPos)
    exitShift.setDuration(100)  


    def enterEvent(e):  
        button.setProperty(b'pos',exitShift.endValue())
        enterShift.start()
        Effects.dropShadow(button,1,2)
        

    def leaveEvent(e):
        exitShift.start()
        Effects.dropShadow(button)
    

    button.enterEvent=enterEvent
    button.leaveEvent=leaveEvent

在 运行 上,一旦鼠标进入 QPushbutton,它就会移动到其父窗口小部件的左上角,动画开始正常运行。我不明白为什么会这样。但是我能够得到它,只有当我在动画中使用任何静态值时才会发生。

静态位置的按钮动画:

这是一个例子:

import sys
from PyQt5.QtCore import QEvent, QPoint, QObject, QPropertyAnimation
from PyQt5.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
# This is the same method mentioned above
from styling import addButtonHoverAnimation


class Widget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        layout=QVBoxLayout()

        button1 = QPushButton("Proceed1", self)
        layout.addWidget(button1)

        button2 = QPushButton("Proceed2", self)
        layout.addWidget(button2)
        self.setLayout(layout)

        self.resize(640, 480)
        addButtonHoverAnimation(button1)
        addButtonHoverAnimation(button2)


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

    view = Widget()
    view.show()

    ret = app.exec_()
    sys.exit(ret)


if __name__ == "__main__":
    main()

问题是可能当状态从enter变为leave时(反之亦然)之前的动画还没有结束所以widget的位置不是初始位置也不是最终位置,所以当开始新的时候动画有一个累积的偏差。一种可能的解决方案是初始化位置并保留它作为参考。

另一方面,你不应该这样做 x.fooMethod = foo_callable 因为很多都可能失败,在这种情况下最好使用事件过滤器。

import sys
from dataclasses import dataclass
from functools import cached_property

from PyQt5.QtCore import QEvent, QPoint, QObject, QPropertyAnimation
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget


@dataclass
class AnimationManager(QObject):
    widget: QWidget
    delta: QPoint = QPoint(3, 3)
    duration: int = 100

    def __post_init__(self):
        super().__init__(self.widget)
        self._start_value = QPoint()
        self._end_value = QPoint()
        self.widget.installEventFilter(self)
        self.animation.setTargetObject(self.widget)
        self.animation.setPropertyName(b"pos")
        self.reset()

    def reset(self):
        self._start_value = self.widget.pos()
        self._end_value = self._start_value + self.delta
        self.animation.setDuration(self.duration)

    @cached_property
    def animation(self):
        return QPropertyAnimation(self)

    def eventFilter(self, obj, event):
        if obj is self.widget:
            if event.type() == QEvent.Enter:
                self.start_enter_animation()
            elif event.type() == QEvent.Leave:
                self.start_leave_animation()
        return super().eventFilter(obj, event)

    def start_enter_animation(self):
        self.animation.stop()
        self.animation.setStartValue(self.widget.pos())
        self.animation.setEndValue(self._end_value)
        self.animation.start()

    def start_leave_animation(self):
        self.animation.stop()
        self.animation.setStartValue(self.widget.pos())
        self.animation.setEndValue(self._start_value)
        self.animation.start()


class Widget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        button1 = QPushButton("Proceed1", self)
        button1.move(100, 100)

        button2 = QPushButton("Proceed2", self)
        button2.move(200, 200)

        self.resize(640, 480)

        animation_manager1 = AnimationManager(widget=button1)
        animation_manager2 = AnimationManager(widget=button2)


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

    view = Widget()
    view.show()

    ret = app.exec_()
    sys.exit(ret)


if __name__ == "__main__":
    main()

184 / 5000 翻译结果 如果您正在使用布局,那么您必须重置位置,因为布局不会立即应用位置更改,而是仅在父小部件应用更改时才应用。

class Widget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        button1 = QPushButton("Proceed1")
        button2 = QPushButton("Proceed2")

        lay = QVBoxLayout(self)
        lay.addWidget(button1)
        lay.addWidget(button2)

        self.resize(640, 480)

        self.animation_manager1 = AnimationManager(widget=button1)
        self.animation_manager2 = AnimationManager(widget=button2)

    def resizeEvent(self, event):
        super().resizeEvent(event)
        self.animation_manager1.reset()
        self.animation_manager2.reset()