You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 05:30:47