PySide6复刻tympanus两款自定义动画按钮效果求助
PySide6 自定义悬停动画按钮修正方案
核心问题汇总
- 3号斜切扫动按钮
- 斜边形状计算错误:原代码误用角度传入弧度制三角函数,生成的多边形不符合原设计的固定斜角扫动效果
- 文字颜色无分层:原设计扫过区域文字为黑色、未扫过区域为白色,现有实现为全局统一文字色
- 动画对象管理混乱:每次进出事件都新建动画对象、重复绑定信号,易造成内存泄漏和动画冲突
- 19号圆形扩散按钮
- 扩散起点错误:原设计圆形从鼠标进入按钮的位置开始扩散,现有实现固定从按钮中心扩散
- 边框动效逻辑错误:原设计悬停时边框向外扩张并渐隐,现有实现为内缩调整
- 同样存在文字分层缺失、动画信号重复绑定问题
修正后完整代码
3号斜切扫动按钮(EButton3)
# -*- coding: utf-8 -*- import sys import math from PySide6.QtWidgets import * from PySide6.QtCore import * from PySide6.QtGui import * class EButton3(QPushButton): Radius = 10 _m_Text = "" _m_hover = False _animateVal = 0 def __init__(self, parent=None): super().__init__(parent) self.setText("Button") # 复用同一个动画对象,避免重复创建 self._ani = QPropertyAnimation(self, b'AnimateVal', self) self._ani.setDuration(450) self._ani.valueChanged.connect(self.update) def getText(self): return self._m_Text def setText(self, Text): self._m_Text = Text self.update() def isHover(self): return self._m_hover @Property(int) def AnimateVal(self): return self._animateVal @AnimateVal.setter def AnimateVal(self, val): self._animateVal = val def paintEvent(self, event: QPaintEvent): painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.setPen(Qt.NoPen) # 绘制基础黑色背景 bg_path = QPainterPath() base_rect = QRectF(0, 0, self.width(), self.height()) bg_path.addRoundedRect(base_rect, self.Radius, self.Radius) painter.setBrush(QColor(Qt.black)) painter.drawPath(bg_path) anval = self.AnimateVal / 100 if self.AnimateVal > 0: # 固定斜角斜率,生成扫动的平行四边形 skew_offset = self.height() * 0.6 sweep_poly = QPolygonF([ QPointF(self.width() * anval + skew_offset, 0), QPointF(self.width() * anval - skew_offset, self.height()), QPointF(-skew_offset*2, self.height()), QPointF(-skew_offset*2, 0) ]) sweep_path = QPainterPath() sweep_path.addPolygon(sweep_poly) # 裁剪在按钮圆角范围内 painter.setClipPath(bg_path) painter.setBrush(QColor(231, 231, 231)) painter.drawPath(sweep_path) # 分层绘制文字:扫过区域黑色,未扫过区域白色 painter.setClipping(False) # 先画全按钮白色文字 painter.setPen(QColor(Qt.white)) painter.drawText(self.rect(), Qt.AlignCenter, self.getText()) # 裁剪到扫动区域画黑色文字 painter.setClipPath(sweep_path.intersected(bg_path)) painter.setPen(QColor(Qt.black)) painter.drawText(self.rect(), Qt.AlignCenter, self.getText()) else: # 未触发动画时直接画白色文字 painter.setPen(QColor(Qt.white)) painter.drawText(self.rect(), Qt.AlignCenter, self.getText()) def enterEvent(self, event) -> None: self._m_hover = True self._ani.stop() self._ani.setStartValue(self._animateVal) self._ani.setEndValue(100) self._ani.setEasingCurve(QEasingCurve.OutCubic) self._ani.start() super().enterEvent(event) def leaveEvent(self, event) -> None: self._m_hover = False self._ani.stop() self._ani.setStartValue(self._animateVal) self._ani.setEndValue(0) self._ani.setEasingCurve(QEasingCurve.InCubic) self._ani.start() super().leaveEvent(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())
19号圆形扩散按钮(EButton19)
# -*- coding: utf-8 -*- import sys import math from PySide6.QtWidgets import * from PySide6.QtCore import * from PySide6.QtGui import * class EButton19(QPushButton): Radius = 10 _m_Text = "" _m_hover = False _animateVal = 0 _hover_pos = QPointF() def __init__(self, parent=None): super().__init__(parent) self.setText("Button") self._ani = QPropertyAnimation(self, b'AnimateVal', self) self._ani.setDuration(350) self._ani.valueChanged.connect(self.update) def getText(self): return self._m_Text def setText(self, Text): self._m_Text = Text self.update() def isHover(self): return self._m_hover @Property(int) def AnimateVal(self): return self._animateVal @AnimateVal.setter def AnimateVal(self, val): self._animateVal = val def paintEvent(self, event: QPaintEvent): painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.setPen(Qt.NoPen) base_rect = QRectF(0, 0, self.width(), self.height()) bg_path = QPainterPath() bg_path.addRoundedRect(base_rect, self.Radius, self.Radius) # 绘制浅灰色基础背景 painter.setBrush(QColor(231,231,231)) painter.drawPath(bg_path) anval = self._animateVal / 100 # 计算扩散半径,取到按钮四个角的最大距离保证完全覆盖 max_radius = math.hypot(max(self._hover_pos.x(), self.width()-self._hover_pos.x()), max(self._hover_pos.y(), self.height()-self._hover_pos.y())) if anval > 0: # 绘制扩散圆形 painter.setClipPath(bg_path) painter.setBrush(QColor(Qt.black)) painter.drawEllipse(self._hover_pos, max_radius * anval, max_radius * anval) painter.setClipping(False) # 绘制外扩渐隐边框 border_pad = 10 * anval border_path = QPainterPath() border_rect = base_rect.adjusted(-border_pad, -border_pad, border_pad, border_pad) border_path.addRoundedRect(border_rect, self.Radius + border_pad, self.Radius + border_pad) painter.setPen(QPen(QColor(0,0,0, 255*(1-anval)), 2)) painter.setBrush(Qt.NoBrush) painter.drawPath(border_path) painter.setPen(Qt.NoPen) # 分层绘制文字 painter.setClipping(False) painter.setPen(QColor(Qt.black)) painter.drawText(self.rect(), Qt.AlignCenter, self.getText()) painter.setClipPath(bg_path.intersected(QPainterPath().addEllipse(self._hover_pos, max_radius * anval, max_radius * anval))) painter.setPen(QColor(Qt.white)) painter.drawText(self.rect(), Qt.AlignCenter, self.getText()) else: # 初始状态黑色文字 painter.setPen(QColor(Qt.black)) painter.drawText(self.rect(), Qt.AlignCenter, self.getText()) def enterEvent(self, event): self._m_hover = True # 记录鼠标进入的位置作为扩散起点 self._hover_pos = event.position() self._ani.stop() self._ani.setStartValue(self._animateVal) self._ani.setEndValue(100) self._ani.setEasingCurve(QEasingCurve.OutQuad) self._ani.start() super().enterEvent(event) def leaveEvent(self, event) -> None: self._m_hover = False self._ani.stop() self._ani.setStartValue(self._animateVal) self._ani.setEndValue(0) self._ani.setEasingCurve(QEasingCurve.InQuad) self._ani.start() super().leaveEvent(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())
效果说明
- 修正后的3号按钮实现了固定斜角的扫动效果,文字随扫动区域自动切换颜色,动画平滑无冲突
- 修正后的19号按钮从鼠标进入位置开始圆形扩散,外边框同步向外扩张渐隐,文字颜色随填充区域切换
- 两个按钮均复用动画对象,避免了重复创建对象、重复绑定信号导致的内存泄漏问题,动画进出衔接自然
内容的提问来源于stack exchange,提问作者Dream59
相关产品推荐
相关产品推荐

