Qt按钮鼠标悬停动画异常求助:动画混乱、收缩失效
Qt按钮悬停动画异常修复方案
问题概述
基于Qt框架实现继承自QPushButton的myButton类,通过QPropertyAnimation实现鼠标悬停时的按钮展开/收缩动画,但存在两个核心问题:
- 快速移动鼠标时动画逻辑混乱,出现跳变
- 收缩动画完全无法正常执行
当前效果:
目标效果:
原实现代码如下:
.h文件
class myButton : public QPushButton { Q_OBJECT public: QPropertyAnimation* anim; struct WidgetPos { int x = 0; int y = 0; int w = 0; int h = 0; }; WidgetPos wp; void CreateAnimation(QByteArray propertyName) { if (propertyName == "geometry") { anim = new QPropertyAnimation(this, propertyName); this->anim->setDuration(100); this->anim->setEasingCurve(QEasingCurve::Linear); this->wp.x = this->x(); this->wp.y = this->y(); this->wp.w = this->width(); this->wp.h = this->height(); } } myButton(QWidget* parent = 0) : QPushButton(parent) {} bool eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::Enter) { if (!this->wp.x) this->CreateAnimation("geometry"); this->anim->stop(); this->anim->setStartValue( QRect(this->x(), this->y(), this->width(), this->height())); this->anim->setEndValue( QRect(this->x(), this->y(), (this->wp.w + 200) - this->width(), this->height())); this->anim->start(); } else if (event->type() == QEvent::Leave) { this->anim->stop(); this->anim->setStartValue( QRect(this->x(), this->y(), (this->wp.w + 200) - this->width(), this->height())); this->anim->setEndValue( QRect(this->wp.x, this->wp.x, this->wp.w, this->wp.h)); this->anim->start(); } return QWidget::eventFilter(obj, event); } };
.cpp文件
QtWidgetsApplication::QtWidgetsApplication(QWidget * parent) : QMainWindow(parent) { ui.setupUi(this); QPushButton* btn = new myButton(this); btn->setGeometry(100, 100, 50, 40); btn->setStyleSheet(R"(QPushButton { background-image: url(:/tutorial.png); background-repeat: no-repeat; } )"); QLabel* labl = new QLabel(btn); labl->setObjectName("label"); labl->setGeometry(32, 0, btn->width() + 32, btn->height()); labl->setText("Hello World"); labl->setAlignment(Qt::AlignCenter); labl->show(); btn->installEventFilter(btn); return; }
问题根源
QRect参数计算错误:
- 展开动画的结束宽度逻辑错误:
(this->wp.w + 200) - this->width()会导致宽度值随当前状态波动,正确目标应为wp.w + 200 - 收缩动画的结束矩形写错y坐标:
QRect(this->wp.x, this->wp.x, ...),实际应为wp.y
- 展开动画的结束宽度逻辑错误:
动画状态处理不当:
- 快速切换悬停状态时,未基于按钮当前实际几何状态设置动画起点,而是用了错误的计算值
- 依赖自定义结构体保存初始状态,判断逻辑存在漏洞(初始x为0时才初始化动画,若按钮初始x就是0会重复创建)
事件处理冗余:使用事件过滤器处理悬停事件不如直接重写
enterEvent/leaveEvent直观,易引发事件转发问题
修复后的代码
.h文件
class myButton : public QPushButton { Q_OBJECT private: QPropertyAnimation* m_anim = nullptr; QRect m_originalRect; // 直接用QRect保存初始几何信息 const int m_expandWidth = 200; // 固定展开宽度增量 protected: void enterEvent(QEnterEvent* event) override; void leaveEvent(QEvent* event) override; public: explicit myButton(QWidget* parent = nullptr); ~myButton(); // 新增析构函数释放动画内存 };
.cpp文件
myButton::myButton(QWidget* parent) : QPushButton(parent) { // 初始化动画,避免延迟创建的逻辑漏洞 m_anim = new QPropertyAnimation(this, "geometry"); m_anim->setDuration(150); // 适当延长时长提升流畅度 m_anim->setEasingCurve(QEasingCurve::InOutQuad); // 用缓动曲线让动画过渡更自然 } myButton::~myButton() { delete m_anim; } void myButton::enterEvent(QEnterEvent* event) { QPushButton::enterEvent(event); // 首次进入时保存初始矩形 if (m_originalRect.isNull()) { m_originalRect = this->geometry(); } m_anim->stop(); // 动画起点为按钮当前实际几何状态 m_anim->setStartValue(this->geometry()); // 终点为初始宽度+增量,保持x/y坐标不变 QRect endRect = m_originalRect; endRect.setWidth(m_originalRect.width() + m_expandWidth); m_anim->setEndValue(endRect); m_anim->start(); } void myButton::leaveEvent(QEvent* event) { QPushButton::leaveEvent(event); if (m_originalRect.isNull()) { return; } m_anim->stop(); // 动画起点为按钮当前实际几何状态 m_anim->setStartValue(this->geometry()); // 终点为初始矩形 m_anim->setEndValue(m_originalRect); m_anim->start(); }
主窗口代码调整
QtWidgetsApplication::QtWidgetsApplication(QWidget * parent) : QMainWindow(parent) { ui.setupUi(this); myButton* btn = new myButton(this); btn->setGeometry(100, 100, 50, 40); btn->setStyleSheet(R"(QPushButton { background-image: url(:/tutorial.png); background-repeat: no-repeat; } )"); QLabel* labl = new QLabel(btn); labl->setObjectName("label"); labl->setGeometry(32, 0, btn->width() + 32, btn->height()); labl->setText("Hello World"); labl->setAlignment(Qt::AlignCenter); labl->show(); // 无需再安装事件过滤器,已通过重写事件函数处理悬停 }
额外优化建议
- 重写
resizeEvent,让内部QLabel自动跟随按钮宽度变化,避免手动调整几何信息 - 将动画时长、展开增量设为可配置的成员变量,提升类的复用性
- 使用
QPointer管理动画对象,避免野指针风险
内容的提问来源于stack exchange,提问作者Cesar
相关产品推荐
相关产品推荐

