Qt 自定义动画按钮

Dre*_*m59 3 qt pyqt paintevent pyside pyqt5

我正在尝试创建自定义动画按钮我找到了此页面:按钮示例

我喜欢这个网站上的 3 和 19。我试过做3个,但不一样。有人能帮我吗?

动图

我的网站上第三个按钮的代码:

 # -*- coding: utf-8 -*-
import sys, os, time, math
from PySide6 import QtCore, QtWidgets, QtGui
from PySide6.QtWidgets import *
from PySide6.QtCore import *
from PySide6.QtGui import *


class EButton3(QPushButton):
    AnimateEnabled = True
    Radius = 10

    _m_Text = ""
    def __init__(self, parent=None):
        super(EButton3, self).__init__(None)
        self.enterEvent = self.Custom_enterEvent
        self.leaveEvent = self.Custom_leaveEvent
        self.setText("Button")
    def getText(self):
        return self._m_Text
    def setText(self, Text):
        self._m_Text = Text
        self.update()

    _m_hover=False
    def isHover(self):
        return self._m_hover

    def paintEvent(self, event: QPaintEvent):
        ret = None #QPushButton.paintEvent(self, event)
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        path, path2 = QPainterPath(), QPainterPath()
        BaseBackground, BaseBackgroundHover = QColor(Qt.black), QColor(22,2,22)
        BaseForeground, BaseForegroundHover = QColor(Qt.white), QColor(Qt.black)
        painter.setBrush(QBrush(BaseBackground if not self.isHover() else BaseBackgroundHover))
        painter.setPen(Qt.NoPen)

        rect = QRectF(0, 0, self.width(), self.height())
        path.addRoundedRect(rect, self.Radius, self.Radius)
        painter.drawPath(path)
        painter.setPen(BaseForeground if not self.isHover() else BaseForegroundHover)


        if self.AnimateEnabled:
            painter.setBrush(QBrush(QColor(231, 231, 231)))
            anval = self.AnimateVal / 100
            polygon = QPolygonF([
                QPoint(self.width() * anval, 0),
                QPoint(0, 0),
                QPoint(0, self.height()),
                QPoint((self.width() + 10) * math.sin(anval / 100 * 180), self.height())
            ])
            painter.setClipPath(path)
            path2.addPolygon(polygon)

            painter.drawPath(path2)

        painter.drawText(self.rect(), Qt.AlignCenter, self.getText())
        return ret

    _animateVal = 0
    def setAnimateVal(self, val):
        self._animateVal = val
    def getAnimateVal(self):
        return self._animateVal

    AnimateVal = QtCore.Property(int, getAnimateVal, setAnimateVal)
    temps = []

    def Custom_enterEvent(self, event) -> None:
        self._m_hover = True
        step2_ani = QPropertyAnimation(self, b'AnimateVal')
        step2_ani.setStartValue(self.getAnimateVal())
        step2_ani.setEndValue(100)
        step2_ani.setEasingCurve(QEasingCurve.InQuad)
        step2_ani.setDuration(500)
        self.temps.append(step2_ani)

        def valChanged():
            self.update()

        def finished():
            self.AnimateVal = 100

        step2_ani.valueChanged.connect(valChanged)
        step2_ani.finished.connect(finished)
        step2_ani.start()
        return QPushButton.enterEvent(self, event)

    def Custom_leaveEvent(self, event) -> None:
        self._m_hover = False
        step2_ani = QPropertyAnimation(self, b'AnimateVal')
        self.temps.append(step2_ani) # we need to store it or self.step_ani2 = ... else it will not start
        step2_ani.setStartValue(self.getAnimateVal())
        step2_ani.setEndValue(0)
        step2_ani.setEasingCurve(QEasingCurve.OutCubic)
        step2_ani.setDuration(500)

        def valChanged():
            self.update()

        def finished():
            self.AnimateVal = 0

        step2_ani.valueChanged.connect(valChanged)
        step2_ani.finished.connect(finished)
        step2_ani.start()
        return QPushButton.leaveEvent(self, event)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    wind = QMainWindow()
    wind.setStyleSheet("QMainWindow{background-color:rgb(247,247,250)}")
    wind.resize(150, 80)
    wid = QWidget()
    lay = QHBoxLayout(wid)
    lay.setAlignment(Qt.AlignCenter)

    mycustombutton = EButton3()
    mycustombutton.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
    lay.addWidget(mycustombutton)
    wind.setCentralWidget(wid)
    wind.show()
    sys.exit(app.exec())
Run Code Online (Sandbox Code Playgroud)

我的 19 号代码:按钮

# -*- coding: utf-8 -*-
import sys, os, time, math
from PySide6 import QtCore, QtWidgets, QtGui
from PySide6.QtWidgets import *
from PySide6.QtCore import *
from PySide6.QtGui import *


class EButton19(QPushButton):
    AnimateEnabled = True
    Radius = 10

    _m_Text = ""
    def __init__(self, parent=None):
        super(EButton19, self).__init__(None)
        self.enterEvent = self.Custom_enterEvent
        self.leaveEvent = self.Custom_leaveEvent
        self.setText("Button")
        self.propertyanimation = QPropertyAnimation(self, b'AnimateVal')
        self.propertyanimation.setDuration(350)

    def getText(self):
        return self._m_Text
    def setText(self, Text):
        self._m_Text = Text
        self.update()

    _m_hover=False
    def isHover(self):
        return self._m_hover

    def paintEvent(self, event: QPaintEvent):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        BaseBackground, BaseBackgroundHover = QColor(231,231,231), QColor(Qt.black)
        BaseForeground, BaseForegroundHover = QColor(Qt.black), QColor(Qt.white)
        path, path2 = QPainterPath(), QPainterPath()
        painter.setBrush(QBrush(BaseBackground if not self.isHover() else BaseBackgroundHover))
        painter.setPen(Qt.NoPen)

        rect = QRectF(0, 0, self.width(), self.height())
        anval = self.AnimateVal / 100
        padding = 10
        rect = rect.adjusted(-padding * anval, -padding * anval, padding * anval, padding * anval)
        path.addRoundedRect(rect.adjusted(padding / 2, padding, -padding / 2, -padding), self.Radius, self.Radius)

        painter.drawPath(path)


        if self.AnimateEnabled and self.isHover():
            painter.setBrush(QBrush(QColor(0, 0, 0)))

            painter.setClipPath(path)
            painter.setPen(Qt.black)
            radiusEffect = 75
            path2.addEllipse(self.rect().center(), radiusEffect * anval, radiusEffect * anval)

            painter.drawPath(path2)

        painter.setPen(BaseForeground if not self.isHover() else BaseForegroundHover)
        painter.drawText(self.rect(), Qt.AlignCenter, self.getText())

    _animateVal = 0
    def setAnimateVal(self, val):
        self._animateVal = val
    def getAnimateVal(self):
        return self._animateVal

    AnimateVal = QtCore.Property(int, getAnimateVal, setAnimateVal)

    def Custom_enterEvent(self, event):
        self._m_hover = True
        self.propertyanimation.stop()
        self.propertyanimation.setStartValue(self.getAnimateVal())
        self.propertyanimation.setEndValue(100)
        self.propertyanimation.setEasingCurve(QEasingCurve.InQuad)


        def valChanged():
            self.update()

        def finished():
            self.AnimateVal = 100

        self.propertyanimation.valueChanged.connect(valChanged)
        self.propertyanimation.finished.connect(finished)
        self.propertyanimation.start()
        return QPushButton.enterEvent(self, event)

    def Custom_leaveEvent(self, event) -> None:
        self._m_hover = False
        self.propertyanimation.stop()
        self.propertyanimation.setStartValue(self.getAnimateVal())
        self.propertyanimation.setEndValue(0)
        self.propertyanimation.setEasingCurve(QEasingCurve.OutCubic)

        def valChanged():
            self.update()

        def finished():
            self.AnimateVal = 0

        self.propertyanimation.valueChanged.connect(valChanged)
        self.propertyanimation.finished.connect(finished)
        self.propertyanimation.start()
        return QPushButton.leaveEvent(self, event)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    wind = QMainWindow()
    wind.setStyleSheet("QMainWindow{background-color:rgb(247,247,250)}")
    wind.resize(150, 80)
    wid = QWidget()
    lay = QHBoxLayout(wid)
    lay.setAlignment(Qt.AlignCenter)

    mycustombutton = EButton19()
    mycustombutton.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
    lay.addWidget(mycustombutton)
    wind.setCentralWidget(wid)
    wind.show()
    sys.exit(app.exec())
Run Code Online (Sandbox Code Playgroud)

我的代码外观很糟糕(如果你运行它,你就会看到)。我需要帮助!(适用于第 3 个和第 19 个按钮)

谢谢!

mus*_*nte 5

您的代码的主要问题是您使用基于浮点的矩形进行剪切,但绘画仍然发生在概念上整数的坐标中,并且也忽略了画笔宽度。

您还使您的代码比应有的更加复杂,没有考虑使用不同命名的方法“覆盖”进入/离开事件,而不是正确覆盖基本实现。

我建议采用一种更干净的方法,最重要的是,考虑正确的剪切坐标,并且还使用单个动画而不是不断创建新动画(这最终可能导致不必要的内存使用,因为这些动画在完成后不会被销毁。

重要的部分是setClipPath()使用IntersectClip参数,因此只会为合并路径的结果设置剪切(仅使用它们的公共区域)。

最后,并不总是需要为 Qt 动画创建自定义属性:QVariantAnimation通常就足够了(并且高效,除非过度使用它们),因为您只在需要时访问它,而不是调用 setter Property

在讨论建议的解决方案之前,还有一些其他注意事项:

  • 创建不会在(可能)非常频繁调用的函数中使用的变量是一个糟糕的主意;每次绘制小部件时为悬停状态创建背景颜色有什么意义,即使小部件根本没有悬停?
  • 局部函数(类似于 lambda)仅应在有意义时使用;您要么拥有基于显式(而非持久)局部变量的局部函数,要么正确创建方法
  • 除了非常特殊的情况外,如果您的动画几乎总是做同样的事情,那么您不应该在需要时创建新动画,特别是当您不确定是否删除它时;否则,您最终可能会得到数以万计的持续动画对象,这些对象毫无意义地占用内存。
class Button(QtWidgets.QPushButton):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.backgroundColors = (
            QtGui.QColor(QtCore.Qt.black), 
            QtGui.QColor(QtCore.Qt.lightGray)
        )
        self.foregroundColors = (
            QtGui.QColor(QtCore.Qt.white), 
            QtGui.QColor(QtCore.Qt.black)
        )

        font = self.font()
        font.setBold(True)
        self.setFont(font)

        self.hoverAnimation = QtCore.QVariantAnimation(self)
        # NOTE: both start and end values *must* be floats, otherwise the
        # animation will just "jump" from 0 to 1 and vice versa!
        self.hoverAnimation.setStartValue(0.)
        self.hoverAnimation.setEndValue(1.)
        self.hoverAnimation.setEasingCurve(QtCore.QEasingCurve.OutCubic)
        self.hoverAnimation.setDuration(400)
        self.hoverAnimation.valueChanged.connect(self.update)

    def enterEvent(self, event):
        super().enterEvent(event)
        self.hoverAnimation.setDirection(self.hoverAnimation.Forward)
        self.hoverAnimation.start()

    def leaveEvent(self, event):
        super().leaveEvent(event)
        self.hoverAnimation.setDirection(self.hoverAnimation.Backward)
        self.hoverAnimation.start()

    def paintEvent(self, event):
        qp = QtGui.QPainter(self)
        qp.setRenderHint(qp.Antialiasing)
        qp.save()

        radius = max(4, min(self.height(), self.width()) * .125)
        clipRect = QtCore.QRectF(self.rect().adjusted(0, 0, -1, -1))
        borderPath = QtGui.QPainterPath()
        borderPath.addRoundedRect(clipRect, radius, radius)
        qp.setClipPath(borderPath)

        qp.fillRect(self.rect(), self.backgroundColors[0])
        qp.setPen(self.foregroundColors[0])
        qp.drawText(self.rect(), 
            QtCore.Qt.AlignCenter|QtCore.Qt.TextShowMnemonic, self.text())

        aniValue = self.hoverAnimation.currentValue()
        if aniValue:
            # use an arbitrary "center" for the radius, based on the widget size
            extent = min(self.height(), self.width()) * 3
            angle = atan(extent / self.width())
            reference = cos(angle) * (extent + self.width())
            x = self.width() - reference
            ratio = 1 - aniValue

            hoverPath = QtGui.QPainterPath()
            hoverPath.moveTo(x, 0)
            hoverPath.lineTo(self.width() - reference * ratio, 0)
            hoverPath.lineTo(self.width(), extent)
            hoverPath.lineTo(x, extent)
            qp.setClipPath(hoverPath, QtCore.Qt.IntersectClip)

            qp.fillRect(self.rect(), self.backgroundColors[1])
            qp.setPen(self.foregroundColors[1])
            qp.drawText(self.rect(), QtCore.Qt.AlignCenter|QtCore.Qt.TextShowMnemonic, self.text())

        qp.restore()
        qp.translate(.5, .5)
        qp.drawRoundedRect(clipRect, radius, radius)
Run Code Online (Sandbox Code Playgroud)

注意:上面的实现还不完美(它有一些非常宽和短的按钮问题。我最终会尽快更新这个答案,只需将代码视为概念参考即可。