Qt6实现鼠标悬停时图标并行淡入淡出的按钮开发需求
Qt6实现按钮图标并行淡入淡出动画
需求说明
需要将原有QToolButton的顺序淡入淡出动画(先淡出旧图标,再淡入新图标)修改为并行动画:鼠标悬停时,普通图标淡出与悬停图标淡入同时执行;鼠标离开时,悬停图标淡出与普通图标淡入同时执行,使用QParallelAnimationGroup实现该效果。
核心修改思路
原有实现通过切换单个图标+顺序动画完成过渡,无法实现双图标并行显示。重构方案要点:
- 在按钮内部添加两个重叠的
QLabel,分别承载普通图标和悬停图标 - 给每个图标层单独设置
QGraphicsOpacityEffect,实现独立的透明度控制 - 使用
QParallelAnimationGroup同时执行两个图标层的透明度动画 - 处理鼠标快速进出时的动画中断与资源清理
修改后的代码
头文件(FadingImagesPushButton.h)
#ifndef FADINGIMAGESPUSHBUTTON_H #define FADINGIMAGESPUSHBUTTON_H #include <QToolButton> #include <QPropertyAnimation> #include <QGraphicsOpacityEffect> #include <QParallelAnimationGroup> #include <QLabel> class FadingImagesPushButton : public QToolButton { Q_OBJECT public: FadingImagesPushButton(QWidget *parent = nullptr, const QString &title = "", const QString &imageNormalPath = "", const QString &imageHoverPath = ""); protected: static const int s_AnimDuration; virtual bool event(QEvent *e) override; void hoverEnter(); void hoverLeave(); QString m_imageNormalPath; QString m_imageHoverPath; // 双图标层:分别承载普通和悬停图标 QLabel *m_normalIconLabel; QLabel *m_hoverIconLabel; // 各自的透明度效果 QGraphicsOpacityEffect *m_normalOpacityEffect; QGraphicsOpacityEffect *m_hoverOpacityEffect; // 并行动画组 QParallelAnimationGroup *m_hoverAnimGroup; QParallelAnimationGroup *m_leaveAnimGroup; protected slots: void clearHoverAnimGroup(); void clearLeaveAnimGroup(); }; #endif // FADINGIMAGESPUSHBUTTON_H
源文件(FadingImagesPushButton.cpp)
#include "fadingimagespushbutton.h" #include <QEvent> #include <QVBoxLayout> const int FadingImagesPushButton::s_AnimDuration = 800; FadingImagesPushButton::FadingImagesPushButton(QWidget *parent, const QString &title, const QString &imageNormalPath, const QString &imageHoverPath) : QToolButton(parent), m_imageNormalPath(imageNormalPath), m_imageHoverPath(imageHoverPath) { setToolButtonStyle(Qt::ToolButtonTextUnderIcon); setFixedSize(275, 280); setAttribute(Qt::WA_Hover); QFont font; font.setPointSize(18); setFont(font); // 构建内部布局:图标层在上,文字在下 QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSpacing(5); // 图标容器,承载两个重叠的图标层 QWidget *iconContainer = new QWidget(this); iconContainer->setFixedSize(270, 240); iconContainer->setLayout(new QVBoxLayout(iconContainer)); iconContainer->layout()->setContentsMargins(0, 0, 0, 0); // 普通图标层(初始完全显示) m_normalIconLabel = new QLabel(iconContainer); m_normalIconLabel->setPixmap(QPixmap(m_imageNormalPath).scaled(270, 240, Qt::KeepAspectRatio, Qt::SmoothTransformation)); m_normalIconLabel->setAlignment(Qt::AlignCenter); m_normalOpacityEffect = new QGraphicsOpacityEffect(this); m_normalOpacityEffect->setOpacity(1.0); m_normalIconLabel->setGraphicsEffect(m_normalOpacityEffect); iconContainer->layout()->addWidget(m_normalIconLabel); // 悬停图标层(初始完全透明,叠在普通图标上方) m_hoverIconLabel = new QLabel(iconContainer); m_hoverIconLabel->setPixmap(QPixmap(m_imageHoverPath).scaled(270, 240, Qt::KeepAspectRatio, Qt::SmoothTransformation)); m_hoverIconLabel->setAlignment(Qt::AlignCenter); m_hoverOpacityEffect = new QGraphicsOpacityEffect(this); m_hoverOpacityEffect->setOpacity(0.0); m_hoverIconLabel->setGraphicsEffect(m_hoverOpacityEffect); iconContainer->layout()->addWidget(m_hoverIconLabel); mainLayout->addWidget(iconContainer); // 文字标签(替代原按钮setText,避免与图标层布局冲突) QLabel *textLabel = new QLabel(title, this); textLabel->setFont(font); textLabel->setAlignment(Qt::AlignCenter); mainLayout->addWidget(textLabel); // 初始化动画组 m_hoverAnimGroup = nullptr; m_leaveAnimGroup = nullptr; } bool FadingImagesPushButton::event(QEvent *e) { switch (e->type()) { case QEvent::HoverEnter: hoverEnter(); break; case QEvent::HoverLeave: hoverLeave(); break; default: break; } return QToolButton::event(e); } void FadingImagesPushButton::hoverEnter() { // 停止并清理正在执行的离开动画 if (m_leaveAnimGroup) { m_leaveAnimGroup->stop(); clearLeaveAnimGroup(); } // 创建并行动画组:普通图标淡出 + 悬停图标淡入 m_hoverAnimGroup = new QParallelAnimationGroup(this); // 普通图标淡出动画:当前透明度 → 0.0 QPropertyAnimation *normalFadeOut = new QPropertyAnimation(m_normalOpacityEffect, "opacity"); normalFadeOut->setDuration(s_AnimDuration); normalFadeOut->setStartValue(m_normalOpacityEffect->opacity()); normalFadeOut->setEndValue(0.0); normalFadeOut->setEasingCurve(QEasingCurve::InBack); // 悬停图标淡入动画:当前透明度 → 1.0 QPropertyAnimation *hoverFadeIn = new QPropertyAnimation(m_hoverOpacityEffect, "opacity"); hoverFadeIn->setDuration(s_AnimDuration); hoverFadeIn->setStartValue(m_hoverOpacityEffect->opacity()); hoverFadeIn->setEndValue(1.0); hoverFadeIn->setEasingCurve(QEasingCurve::InBack); m_hoverAnimGroup->addAnimation(normalFadeOut); m_hoverAnimGroup->addAnimation(hoverFadeIn); connect(m_hoverAnimGroup, &QParallelAnimationGroup::finished, this, &FadingImagesPushButton::clearHoverAnimGroup); m_hoverAnimGroup->start(); } void FadingImagesPushButton::hoverLeave() { // 停止并清理正在执行的悬停动画 if (m_hoverAnimGroup) { m_hoverAnimGroup->stop(); clearHoverAnimGroup(); } // 创建并行动画组:悬停图标淡出 + 普通图标淡入 m_leaveAnimGroup = new QParallelAnimationGroup(this); // 悬停图标淡出动画:当前透明度 → 0.0 QPropertyAnimation *hoverFadeOut = new QPropertyAnimation(m_hoverOpacityEffect, "opacity"); hoverFadeOut->setDuration(s_AnimDuration); hoverFadeOut->setStartValue(m_hoverOpacityEffect->opacity()); hoverFadeOut->setEndValue(0.0); hoverFadeOut->setEasingCurve(QEasingCurve::InBack); // 普通图标淡入动画:当前透明度 → 1.0 QPropertyAnimation *normalFadeIn = new QPropertyAnimation(m_normalOpacityEffect, "opacity"); normalFadeIn->setDuration(s_AnimDuration); normalFadeIn->setStartValue(m_normalOpacityEffect->opacity()); normalFadeIn->setEndValue(1.0); normalFadeIn->setEasingCurve(QEasingCurve::InBack); m_leaveAnimGroup->addAnimation(hoverFadeOut); m_leaveAnimGroup->addAnimation(normalFadeIn); connect(m_leaveAnimGroup, &QParallelAnimationGroup::finished, this, &FadingImagesPushButton::clearLeaveAnimGroup); m_leaveAnimGroup->start(); } void FadingImagesPushButton::clearHoverAnimGroup() { m_hoverAnimGroup->deleteLater(); m_hoverAnimGroup = nullptr; } void FadingImagesPushButton::clearLeaveAnimGroup() { m_leaveAnimGroup->deleteLater(); m_leaveAnimGroup = nullptr; }
效果说明
- 鼠标悬停时,普通图标从完全显示逐渐透明,同时悬停图标从完全透明逐渐显示,两个动画并行完成
- 鼠标离开时,悬停图标逐渐透明,同时普通图标逐渐显示,动画逻辑与悬停时相反
- 支持鼠标快速进出的中断处理,动画会从当前状态平滑过渡,不会出现跳变
内容的提问来源于stack exchange,提问作者Amir Hammoutene
相关产品推荐
相关产品推荐

