自定义开关控件应继承 QPushButton,重写 paintEvent 绘制背景、滑块和文字,用 QPropertyAnimation 驱动 offset 属性实现平滑滑动,接管 mousePressEvent 和 mouseReleaseEvent 控制状态切换,并在 resizeEvent 中重算滑块参数。

直接用 QPushButton 加样式表最省事,但要带滑动动画、颜色可配、状态可控,必须自定义控件——继承 QWidget 或 QPushButton 都行,关键在三件事:重写 paintEvent、接管鼠标事件、用动画驱动偏移量。
用 QPropertyAnimation 驱动滑块位置
滑动效果本质是圆球(thumb)从左到右平移。不能靠定时器手动刷新坐标再 repaint(),容易卡顿且难控制缓动曲线。正确做法是绑定一个可动画的属性:
-
Q_PROPERTY(int offset READ offset WRITE setOffset)声明偏移量为动画属性 - 构造时创建
QPropertyAnimation,目标对象为this,属性名为"offset" -
setOffset()内部调用update()触发重绘,paintEvent用该值计算圆球x坐标 - 动画时长建议设为
150~200ms,QEasingCurve::InOutQuad比线性更自然
paintEvent 里怎么画背景和滑块
绘制分三层:背景矩形、滑块圆球、(可选)状态文字或提示光。注意几个易错点:
- 背景宽度固定为控件宽,高度取
height(),圆角用QPainterPath或QRectF的adjusted()+drawRoundedRect - 滑块直径 =
height() - 8(留出上下边距),圆心x=m_offset + 滑块半径,y=height()/2 - 务必开启抗锯齿:
painter.setRenderHint(QPainter::Antialiasing),否则圆边发虚 - 不要在
paintEvent里做耗时操作(如字符串测量、路径重建),提前算好复用
鼠标事件必须拦截 mousePressEvent 和 mouseReleaseEvent
只重写 mouseReleaseEvent 不够——用户按住不放再拖出按钮区域,松开时不应触发切换。正确逻辑是:
-
mousePressEvent中调用event->accept()并记录按下状态,避免父控件截获 -
mouseReleaseEvent先判断是否仍在按钮内(rect().contains(event->pos())),再切换状态 - 状态变更后调用
setChecked(!m_checked)(若继承QPushButton)或直接改成员变量并触发toggled(m_checked)信号 - 禁用原生
checkable:setCheckable(false),否则会和自定义逻辑冲突
继承 QPushButton 还是 QWidget?
选 QPushButton 更轻量,能复用其焦点、快捷键、setEnabled() 等行为;但要注意它默认响应空格键触发点击,若不需要得重写 keyPressEvent 过滤掉。选 QWidget 更自由,比如想加文字标签或图标,但得自己补全交互细节。实际项目中,90% 场景用 QPushButton 子类就够了——只要别忘了在构造函数里关掉原生选中逻辑。
最常被忽略的是 resizeEvent:控件缩放时,m_ballRadius 和初始 m_offset 必须重算,否则滑块错位或溢出。这个钩子函数不是可选项。











