实现QComboBox项背景的Hover平滑过渡动画(PySide6)
实现QComboBox项悬停时的背景色平滑过渡动画
要实现QComboBox列表项在鼠标悬停时300ms内从灰色(QColor(198, 198, 198))平滑过渡到白色(QColor(255, 255, 255)),你需要在QStyledItemDelegate中结合QVariantAnimation管理颜色渐变过程,而非直接切换颜色。以下是完整实现方案:
核心思路
- 用字典记录每个列表项的动画实例和当前颜色状态,确保每项动画独立运行
- 重写
editorEvent捕获鼠标进入/离开事件,触发对应方向的颜色动画 - 在
paint函数中使用动画的当前颜色绘制背景,实现平滑过渡
完整代码
from PySide6.QtCore import * from PySide6.QtWidgets import * from PySide6.QtGui import * import sys class ComboBoxDelegate(QStyledItemDelegate): def __init__(self, parent=None): super().__init__(parent) # 存储每个项的动画和当前颜色:key为index的row,value为(animation, current_color) self._animations = {} self.base_color = QColor(198, 198, 198) self.hover_color = QColor(255, 255, 255) self.animation_duration = 300 # 动画时长,单位ms def editorEvent(self, event, model, option, index): # 捕获鼠标进入和离开事件 if event.type() == QEvent.MouseMove or event.type() == QEvent.MouseButtonPress: # 鼠标移动到项上,启动从base到hover的动画 self._start_animation(index, self.base_color, self.hover_color) elif event.type() == QEvent.Leave: # 鼠标离开项,启动从hover到base的动画 self._start_animation(index, self.hover_color, self.base_color) return super().editorEvent(event, model, option, index) def _start_animation(self, index, start_color, end_color): row = index.row() # 如果已有该项的动画,先停止并移除 if row in self._animations: anim, _ = self._animations[row] anim.stop() anim.deleteLater() del self._animations[row] # 创建颜色动画 anim = QVariantAnimation(self) anim.setStartValue(start_color) anim.setEndValue(end_color) anim.setDuration(self.animation_duration) anim.setEasingCurve(QEasingCurve.InOutQuad) # 平滑缓动曲线 # 初始化当前颜色为起始色 current_color = start_color self._animations[row] = (anim, current_color) # 动画值变化时更新颜色并触发重绘 def update_color(value): nonlocal current_color current_color = value self._animations[row] = (anim, current_color) # 触发该项重绘 parent = self.parent() if isinstance(parent, QComboBox): view = parent.view() view.update(index) anim.valueChanged.connect(update_color) # 动画结束后清理资源 anim.finished.connect(lambda: self._clean_up_animation(row)) anim.start() def _clean_up_animation(self, row): if row in self._animations: anim, _ = self._animations[row] anim.deleteLater() del self._animations[row] def paint(self, painter, option, index): row = index.row() # 获取当前颜色:有动画用动画当前值,否则用基础色 if row in self._animations: _, current_color = self._animations[row] else: # 检查是否处于悬停状态(处理动画未启动的边界情况) if option.state & QStyle.State_MouseOver: current_color = self.hover_color else: current_color = self.base_color # 绘制背景 painter.fillRect(option.rect, current_color) # 绘制文本(保留原有样式的对齐等) painter.drawText(option.rect, Qt.AlignVCenter | Qt.AlignLeft, index.data()) class MainWidget(QWidget): def __init__(self): super().__init__() self.combo_box = QComboBox(self) self.combo_box.addItems(['Item 1','Item 2', 'Item 3']) # 确保下拉视图能正确接收鼠标事件 self.combo_box.view().setMouseTracking(True) self.main_layout = QVBoxLayout() self.main_layout.addWidget(self.combo_box) self.setLayout(self.main_layout) delegate = ComboBoxDelegate(self.combo_box) self.combo_box.setItemDelegate(delegate) app = QApplication(sys.argv) window = MainWidget() window.show() sys.exit(app.exec())
关键细节说明
- 启用
view().setMouseTracking(True):确保QComboBox的下拉视图能捕获鼠标移动事件,否则仅点击时才会触发悬停状态 - 使用
QEasingCurve.InOutQuad:让动画的开始和结束阶段速度更平缓,视觉效果更自然 - 动画结束后清理资源:避免内存泄漏,同时保证项在无动画时恢复到正确的基础色
- 边界状态处理:在
paint中直接检查悬停状态,防止动画未及时启动时出现颜色跳变
内容的提问来源于stack exchange,提问作者yakubszatkowski
相关产品推荐
相关产品推荐

