【问题标题】:Adding animation to QPushbutton enterEvent and exitEvent向 QPushbutton enterEvent 和 exitEvent 添加动画
【发布时间】:2021-06-03 02:57:47
【问题描述】:

我正在尝试将自定义动画添加到 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() 甚至完成之前被触发,并且还因为 start 和 end 值是动态的。因此,我尝试提供 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()

【问题讨论】:

标签: python python-3.x pyqt pyqt5


【解决方案1】:

问题是,可能当状态从进入更改为离开(反之亦然)时,前一个动画仍然没有结束,所以小部件的位置不是初始或最终位置,所以当开始新动画时是累积的偏差。一种可能的解决方案是初始化位置并将其作为参考。

另一方面,您不应该使用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()

【讨论】:

  • 我完全明白你的代码和你所做的。我喜欢这种方法,并感谢您的提示。如果我运行此代码,它工作正常。但是如果我在我的应用程序中使用相同的代码,那么出于某种原因会出现第二个问题。按钮位于父级的左上角。
  • @SatyamNiranjan 我无法分析我没有见过的代码,所以我需要一个 MRE,我没有要求你放置一段代码。因此,如果您需要更多帮助,则必须提供 MRE。
  • 按钮是否在布局中对此有影响?因为看起来按钮正在移出布局并粘在父级中的默认位置。
  • @SatyamNiranjan 它确实有效果。我的代码不假设这一点,所以我要求你提供一个 MRE。我不想根据您的布局猜想来实现代码,在您指出它也不起作用之后,我再次向您询问(几乎是在乞求您)MRE,您只需提供它,让我们节省时间吧。
  • 很抱歉给您带来不便。我已经添加了 UI 文件以及对话框的基本代码。
猜你喜欢
  • 2021-09-03
  • 2018-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-04
  • 2019-07-26
  • 2011-04-13
  • 2014-01-11
相关资源
最近更新 更多