VButton 自定义按钮组件
概述
VButton 是一个基于 Qt 的自定义按钮组件,使用纯 painter 绘制,不依赖 QSS 样式表,避免样式污染问题。
功能特性
- 纯 painter 绘制,无 QSS 依赖
- 8 种内置样式变体(Variant),覆盖常见按钮需求
- 自定义颜色支持(ColorOutlined / ColorGhost 模式)
- 平滑的 hover / press 动画过渡
- 涟漪(Ripple)点击动效,Material Design 风格,支持动态配置
- 支持图标 + 文本组合显示
- 支持长按功能
- 圆角背景设计
- 焦点环显示(1s 自动渐隐动画)
- 支持 VMenu 弹出菜单,菜单关闭时自动重置按压状态
- D-Pointer (Pimpl) 架构,头文件精简
样式变体(Variant)
| 变体 | 说明 | 背景 | 边框 | 文字 |
|---|
Default | 默认白色按钮 | 白色/灰色(hover) | 浅灰 | 主题色 |
Primary | 主题色实心按钮 | 主题色 | 主题色 | 白色 |
Outlined | 轮廓按钮 | 透明 | 主题色 | 主题色 |
Filled | 主题色淡背景按钮 | 主题色低alpha | 透明 | 主题色 |
Ghost | 幽灵按钮 | 透明 | 透明 | 主题色 |
Dashed | 虚线边框按钮 | 透明 | 主题色(虚线) | 主题色 |
ColorOutlined | 彩色轮廓按钮(可自定义颜色) | 自定义色低alpha | 自定义色 | 自定义色 |
ColorGhost | 彩色幽灵按钮(可自定义颜色) | 透明 | 透明 | 主题色 |
ColorGhost 变体
ColorGhost 是 Ghost 模式的增强版,文字跟随主题色(亮色→黑色,暗色→白色),hover/pressed 时的背景浮色可通过 setColor() 自定义:
| 状态 | 背景 | 边框 | 文字 |
|---|
| Normal | 透明 | 透明 | 主题色 |
| Hover | 自定义色 alpha=10 | 透明 | 主题色 |
| Pressed | 自定义色 alpha=25 | 透明 | 主题色 |
VButton *btn = new VButton("RedGhost", this);
btn->setVariant(VButton::ColorGhost);
btn->setColor(QColor(235, 0, 11)); // hover/pressed 浮色为红色
快速开始
基本使用
#include "VButton.h"
// 创建按钮(仅图标)
VButton *button = new VButton(parent);
button->setIcon(QIcon(":/icons/error.svg"));
button->setIconSize(QSize(16, 16));
button->setFixedSize(32, 32);
// 连接信号
connect(button, &VButton::clicked, this, &MyClass::onButtonClicked);
创建带文本的按钮
VButton *button = new VButton("Primary", parent);
button->setVariant(VButton::Primary); // 设置样式变体
button->setFixedSize(120, 36);
使用颜色自定义
// ColorOutlined 模式——文字和边框使用自定义色
VButton *warningBtn = new VButton("Warning", this);
warningBtn->setVariant(VButton::ColorOutlined);
warningBtn->setColor(QColor(255, 149, 0));
// ColorGhost 模式——文字为主题色,hover 浮色自定义
VButton *ghostBtn = new VButton("RedGhost", this);
ghostBtn->setVariant(VButton::ColorGhost);
ghostBtn->setColor(QColor(235, 0, 11));
使用长按功能
VButton *button = new VButton("+", this);
button->setFixedSize(24, 24);
// 连接长按信号(count 为当前长按触发次数)
connect(button, &VButton::longPressed, this, [=](int count) {
// 长按处理逻辑,count 从 1 开始递增
});
// 连接长按结束信号(totalPressCount 为长按期间总触发次数)
connect(button, &VButton::longPressReleased, this, [=](int totalPressCount) {
// 长按结束处理逻辑
});
// 默认情况下长按每次触发也会发送 clicked() 信号,可通过以下方式关闭
button->setEmitPressedDuringLongPress(false);
API 参考
构造函数
explicit VButton(QWidget *parent = nullptr);
explicit VButton(const QString &text, QWidget *parent = nullptr);
样式变体
enum Variant {
Default, // 默认白色按钮
Primary, // 主题色实心按钮
Outlined, // 轮廓按钮
Filled, // 主题色淡背景按钮
Ghost, // 幽灵按钮(透明,hover 显示浅灰背景)
Dashed, // 虚线边框按钮
ColorOutlined, // 彩色轮廓按钮(自定义颜色,有背景和边框)
ColorGhost // 彩色幽灵按钮(透明,hover 浮色可自定义,文字为主题色)
};
Variant variant() const;
void setVariant(Variant variant);
自定义颜色
QColor color() const;
void setColor(const QColor &color);
信号
| 信号 | 描述 |
|---|
clicked(bool checked = false) | 继承自 QPushButton,按钮点击时触发 |
longPressed(int count) | 按钮长按时触发,初始延迟 500ms,之后每 100ms 重复触发,count 为当前触发次数(从1开始) |
longPressReleased(int totalPressCount) | 长按结束(按钮释放)时触发,totalPressCount 为长按期间总触发次数 |
长按启用
bool isLongPressEnabled() const;
void setLongPressEnabled(bool enable);
| 方法 | 描述 |
|---|
isLongPressEnabled() | 获取长按功能是否启用 |
setLongPressEnabled(bool) | 设置长按功能是否启用,默认为 false(需手动开启) |
长按控制
bool isEmitPressedDuringLongPress() const;
void setEmitPressedDuringLongPress(bool enable);
| 方法 | 描述 |
|---|
isEmitPressedDuringLongPress() | 获取长按时是否同时发射 clicked() 信号 |
setEmitPressedDuringLongPress(bool) | 设置长按时是否同时发射 clicked() 信号,默认为 true |
长按延迟配置
int longPressInitialDelay() const;
void setLongPressInitialDelay(int ms);
int longPressRepeatDelay() const;
void setLongPressRepeatDelay(int ms);
| 方法 | 描述 |
|---|
longPressInitialDelay() / setLongPressInitialDelay() | 获取/设置长按首次触发延迟(ms),默认 500 |
longPressRepeatDelay() / setLongPressRepeatDelay() | 获取/设置长按重复触发间隔(ms),默认 100 |
长按参数
| 参数 | 默认值 | 说明 |
|---|
_initialDelay | 500ms | 首次触发延迟(可通过 setLongPressInitialDelay 修改) |
_repeatDelay | 100ms | 重复触发间隔(可通过 setLongPressRepeatDelay 修改) |
涟漪效果
涟漪点击动效,点击时以鼠标位置为中心扩散半透明圆波,提供清晰的视觉反馈。
bool isRippleEnabled() const;
void setRippleEnabled(bool enable);
QColor rippleColor() const;
void setRippleColor(const QColor &color);
| 方法 | 描述 |
|---|
isRippleEnabled() / setRippleEnabled() | 获取/设置涟漪是否启用,默认 true |
rippleColor() / setRippleColor() | 获取/设置涟漪颜色;传入空 QColor 时自动推导(Primary→白色,其他→主题色) |
涟漪颜色自动推导:
- Primary 变体(深色背景)→ 白色涟漪,与背景形成对比
- 其他变体(浅色背景)→ VTheme::color().primary 主题色涟漪
- 可通过
setRippleColor() 自定义覆盖
VMenu 弹出菜单
VButton 支持绑定自定义 VMenu 弹出菜单,菜单关闭时自动重置按钮按压状态,防止因菜单交互导致 pressProgress 卡住。
VMenu* menu() const;
void setMenu(VMenu* menu);
| 方法 | 描述 |
|---|
menu() | 获取当前绑定的 VMenu |
setMenu(VMenu*) | 设置弹出菜单,内部自动关联 QPushButton::setMenu,并在菜单 aboutToHide 时重置 pressProgress 动画 |
VButton *btn = new VButton("菜单", this);
VMenu *popupMenu = new VMenu(this);
popupMenu->addAction("选项1");
popupMenu->addAction("选项2");
btn->setMenu(popupMenu);
动画属性(Q_PROPERTY)
| 属性 | 类型 | 说明 |
|---|
hoverProgress | qreal | hover 动画进度 0.0–1.0,过渡时间 500ms |
pressProgress | qreal | press 动画进度 0.0–1.0,过渡时间 80ms |
focusRingProgress | qreal | 焦点环动画进度 0.0–1.0,过渡时间 300ms |
设计特点
纯 painter 绘制
- 不使用 QSS 样式表
- 避免样式污染
- 更好的性能和可控性
动画效果
- hover 进出使用 500ms QPropertyAnimation 平滑过渡
- press 按下/释放使用 80ms 快速过渡(颜色渐变)
- 焦点环显示/消失使用 300ms 过渡
- 颜色通过线性插值实现渐变效果
- Primary 模式支持按压缩小阴影效果
涟漪动效(Ripple)
- 点击时以鼠标位置为圆心,扩散半透明圆波
- 半径扩张使用 easeOutCubic 缓动函数
- 透明度从 0.2 线性衰减至 0
- 单次涟漪持续 600ms
- 60fps 定时器驱动,多涟漪可共存叠加
- 自动裁剪到按钮圆角边界,不超出按钮区域
- 空闲时定时器自动停止,零 CPU 开销
圆角设计
- 默认圆角半径 6px,动态适配按钮高度(不超过高度/4)
- 边框宽度 1px,焦点环宽度 2px
焦点环
- 按钮获得焦点时内部绘制半透明焦点环
- 焦点环颜色为主题色 alpha=40%
- 获得焦点 1 秒后自动缓慢渐隐消失
- Filled、Ghost、ColorGhost、ColorOutlined 风格下不显示焦点环
长按功能
- 按钮按下 500ms 后触发第一次长按信号
- 之后每 100ms 重复触发长按信号
- 释放按钮时停止触发
使用示例
示例 1:各种样式变体
VButton *defaultBtn = new VButton("Default", this);
defaultBtn->setVariant(VButton::Default);
defaultBtn->setFixedSize(120, 36);
VButton *primaryBtn = new VButton("Primary", this);
primaryBtn->setVariant(VButton::Primary);
primaryBtn->setFixedSize(120, 36);
VButton *outlinedBtn = new VButton("Outlined", this);
outlinedBtn->setVariant(VButton::Outlined);
outlinedBtn->setFixedSize(120, 36);
VButton *filledBtn = new VButton("Filled", this);
filledBtn->setVariant(VButton::Filled);
filledBtn->setFixedSize(120, 36);
VButton *ghostBtn = new VButton("Ghost", this);
ghostBtn->setVariant(VButton::Ghost);
ghostBtn->setFixedSize(120, 36);
VButton *dashedBtn = new VButton("Dashed", this);
dashedBtn->setVariant(VButton::Dashed);
dashedBtn->setFixedSize(120, 36);
示例 2:ColorGhost 彩色幽灵按钮
VButton *redGhost = new VButton("RedGhost", this);
redGhost->setVariant(VButton::ColorGhost);
redGhost->setColor(QColor(235, 0, 11));
redGhost->setFixedSize(120, 36);
VButton *greenGhost = new VButton("GreenGhost", this);
greenGhost->setVariant(VButton::ColorGhost);
greenGhost->setColor(QColor(52, 199, 89));
greenGhost->setFixedSize(120, 36);
VButton *blueGhost = new VButton("BlueGhost", this);
blueGhost->setVariant(VButton::ColorGhost);
blueGhost->setColor(QColor(22, 119, 255));
blueGhost->setFixedSize(120, 36);
示例 3:图标 + 文字按钮
VButton *btn = new VButton("设置", this);
btn->setIcon(QIcon(":/icons/setting.png"));
btn->setIconSize(QSize(16, 16));
btn->setVariant(VButton::Ghost);
btn->setFixedSize(100, 36);
示例 4:长按按钮
VButton *plusBtn = new VButton("+", this);
plusBtn->setVariant(VButton::Filled);
plusBtn->setFixedSize(48, 48);
connect(plusBtn, &VButton::clicked, [=]() {
// 普通点击处理
});
connect(plusBtn, &VButton::longPressed, [=](int count) {
// 长按处理(连续触发),count 为当前触发次数
});
示例 5:涟漪效果
VButton *rippleBtn = new VButton("Click Me", this);
rippleBtn->setVariant(VButton::Primary);
rippleBtn->setFixedSize(140, 40);
// 关闭涟漪
rippleBtn->setRippleEnabled(false);
// 自定义涟漪颜色
VButton *customRipple = new VButton("Custom", this);
customRipple->setVariant(VButton::Outlined);
customRipple->setRippleColor(QColor(255, 100, 50));
customRipple->setFixedSize(140, 40);
注意事项
- VButton 使用纯 painter 绘制,不依赖 QSS 样式表
- 建议设置固定大小(setFixedSize)
- 支持文本和图标同时显示
- 继承自 QPushButton,可使用所有 QPushButton 的功能
- 长按信号在按钮按下后首次触发,之后按重复间隔触发,
count 从 1 开始递增;延迟可通过 setLongPressInitialDelay() / setLongPressRepeatDelay() 配置(默认 500ms / 100ms) - 长按结束时发射
longPressReleased(int totalPressCount),传递总触发次数 - 默认长按每次触发也会发射
clicked() 信号,可通过 setEmitPressedDuringLongPress(false) 关闭 setColor() 仅在 ColorOutlined 和 ColorGhost 模式下生效- ColorGhost 模式下文字颜色自动跟随主题,不受 setColor 影响
- 涟漪默认开启,传入空 QColor 时颜色自动推导(Primary→白色,其他→主题色)
- 涟漪与 press 动画互不冲突:涟漪提供点击位置反馈,press 动画驱动颜色过渡
- 代码采用 D-Pointer (Pimpl) 架构,实现细节隐藏在 VButtonPrivate / VButtonStyle 中
setMenu(VMenu*) 内部自动关联 QPushButton::setMenu,并在菜单关闭时强制重置 pressProgress 为 0,防止 press 动画卡住
版本历史
v2.0
- 按钮基础高度改为在构造时读取跨组件令牌
VTheme::control().widgetHeight(默认 33)作为初始参考值 - 移除对主题变化的订阅(
onThemeChanged):widgetHeight 仅作为初始化参考,运行时不随主题动态改高度,需固定尺寸请用 setFixedSize()/setFixedHeight() 显式指定,避免破坏外部布局
v1.9
- 新增
menu() / setMenu(VMenu*) 接口,支持绑定自定义 VMenu 弹出菜单 setMenu() 内部关联 QPushButton::setMenu,并在菜单 aboutToHide 时强制重置 pressProgress,防止因菜单交互导致 press 动画卡住
v1.8
- 新增涟漪(Ripple)点击动效:点击位置扩散半透明圆波,easeOutCubic 缓动,600ms 持续
- 涟漪颜色自动推导:Primary 变体用白色,其他用主题色
- 新增
setRippleEnabled() / isRippleEnabled() / setRippleColor() / rippleColor() API - 移除 press 缩放挤压效果和内容偏移(涟漪替代)
v1.7
- 代码分层拆分:VButtonPrivate.h/cpp(数据+生命周期)+ VButtonStyle.h/cpp(绘制逻辑)
- D-Pointer (Pimpl) 重构:VButton 头文件精简至 79 行,所有实现细节隐藏
- 修复动画竞争条件:6 处动画启动点先保存当前值再调用
setCurrentTime(0) - 优化点击反馈:press 动画 80ms + 1.5px 内容偏移 + 2% 缩放挤压
- 长按 timer 改为延迟创建(仅在
_longPressEnabled=true 时) - fontMetrics 复用优化、常量替代魔数、删除未使用函数
v1.5
Warning 变体重命名为 ColorOutlined- 焦点环透明度从 60% 调整为 40%
- 新增焦点环动画:获得焦点后显示 1 秒,然后缓慢消失
Filled、Ghost、ColorGhost、ColorOutlined 风格下不显示焦点环
v1.4
longPressed 信号改为 longPressed(int count),携带当前触发次数- 新增
longPressReleased(int totalPressCount) 信号,长按结束时触发 - 新增
isLongPressEnabled() / setLongPressEnabled() 总开关,默认关闭,需手动开启长按功能 - 新增
isEmitPressedDuringLongPress() / setEmitPressedDuringLongPress(bool) 开关,默认开启,控制长按时是否同时发送 clicked() 信号 - 新增
longPressInitialDelay() / setLongPressInitialDelay() 和 longPressRepeatDelay() / setLongPressRepeatDelay() 接口,支持动态配置长按延迟 - 修复
paintEvent 中遗漏的 drawShadow() 和 drawFocusRing() 调用 - 移除未使用的头文件引用,
Q_ENUMS 升级为 Q_ENUM
v1.3
- 新增
ColorGhost 变体:透明背景 + 主题色文字 + 自定义 hover/pressed 浮色
v1.2
- 新增
ColorOutlined 变体:支持自定义颜色的彩色轮廓按钮 - 新增
setVariant() / variant() 和 setColor() / color() 接口 - 新增所有样式变体(Default / Primary / Outlined / Filled / Ghost / Dashed)
- 新增 hover / press 动画过渡效果
- 新增焦点环绘制
- 新增 Primary 模式阴影效果
v1.1
- 新增文本显示支持
- 新增长按功能
- 新增
longPressed() 信号 - 添加带文本参数的构造函数
v1.0
- 初始版本
- 纯 painter 绘制
- hover 状态效果
- 圆角背景设计
- 图标支持