Skip to content

StatusLED 成品导览

sourcewidget/status-led/ related:自绘控件递进链第 1 环(下一站 toggle-switch · 待产)

StatusLED 是个状态指示灯——服务器面板上那种绿/黄/红小圆点。听起来简单到不值得单做,但我们偏把它当实例库第一件成品:因为它体积小,却能把「一个正经自绘控件」该有的东西占全了——Q_PROPERTY 全套、动画驱动、尺寸契约、踩坑一个不落。这套骨架后面 toggle-switch / circle-progress / speed-meter 都会复用。

本篇是「成品导览」

想直接用成品 → 看这里(架构 / 决策 / 踩坑 / 怎么读)。 想自己从零搓出来 → 转 手搓手册

1. 它做什么

一个 AwesomeQt::StatusLED 控件:

  • 四种状态NORMAL(绿) / WARNING(琥珀) / ERROR(红) / OFFLINE(灰)
  • 状态切换颜色平滑过渡:不突变,300ms OutCubic 缓动过渡(QPropertyAnimation 驱动一个 QColor 属性)
  • 三种闪烁模式None(不闪) / OnOff(生硬明灭) / Breathing(正弦呼吸)
  • 完整 Q_PROPERTYstatus / color / blinkMode / ledSize 四个属性全可被动画 / Qt Designer / State Machine 驱动

跑起来看一眼比读十行描述管用:

bash
cd widget && cmake -B build && cmake --build build
./build/status-led/demo/status_led_demo

2. 架构总览

类关系

一个 StatusLED 同时「拥有」三个动画 / 定时器对象,各司其职、互不干涉:

三个对象写进不同的成员变量color_anim_current_color_(过渡中间色),breathing_anim_breathing_factor_(0..1 亮度因子),onoff_timer_onoff_visible_(开关)。paintEvent 入口才由 applyDisplayTransform 把它们合成成最终绘制色。这是整份代码最关键的设计——后面解释为什么。

文件职责

文件职责
include/status_led.h接口:Q_PROPERTY 四件套 + Status/BlinkMode 枚举 + 公有 API
src/status_led.cpp实现:动画初始化 / 状态切换 / 合成变换 / 自绘
demo/status_led_window.cpp演示:四态静态 / 交互切换 / 多尺寸 / 过渡·闪烁·属性驱动

状态切换 + 呼吸怎么跑起来

重点:颜色过渡写 current_color_,呼吸写 breathing_factor_两个独立变量。所以「边过渡颜色边呼吸」不打架——它们正交。

3. 关键设计决策

① 颜色过渡走 Q_PROPERTY(QColor color),不手写 RGB 插值。 Qt 内置 QColor 插值器(RGB 线性),QPropertyAnimation(this, "color") 直接能用,省掉 lerp 函数。项目里已有先例(examples 的 colorwidget)。代价:绿→红中间一帧偏暗褐(见踩坑②),但 300ms+OutCubic 快速逼近终点,肉眼可接受。

color_anim_ 用持久成员指针,不用 DeleteWhenStopped 和 circulargauge 不同:那种「每次 new、停了自动 delete」的写法在频繁切换时反复 new/delete,且别处持指针会悬空。这里改持久指针 + stop()/setStartValue(current_color_)/start()——从当前显示色接力到新目标,连切不跳变、不崩。

③ 过渡色与呼吸因子解耦,applyDisplayTransform 合成。current_color_(过渡产物)和 breathing_factor_(呼吸产物)是两个独立变量,paintEvent 入口才合成。好处:天然正交、可并行,不用为「过渡中要不要暂停呼吸」设计状态机。

④ BlinkMode 枚举收编三种闪烁,旧 setBlinking 做兼容薄层。None/OnOff/Breathing 一个枚举讲清,setBlinking(bool) 留着映射 OnOff/None,老 demo 不用改。

⑤ 诚实承认 RGB 插值中间色问题,把 HSV 留给进阶挑战。 不装没问题。OutCubic+短时长是务实解;要鲜艳过渡,qRegisterAnimationInterpolator 注册 HSV 插值器是正路(见 手搓手册·进阶挑战)。

4. 怎么读这份 code

按这个顺序读,最快建立心智:

  1. include/status_led.h 的 Q_PROPERTY 四件套(28-31 行)——先看「对外暴露哪些可驱动属性」
  2. setStatussrc/status_led.cpp:90)——状态切换如何启动过渡,盯 stop()+setStartValue(current_color_)+start() 这三行的接力
  3. setAnimatedColorsrc/status_led.cpp:111)——动画每帧回调,纯赋值 + emit + update
  4. applyDisplayTransformsrc/status_led.cpp:194)——合成核心,on-off 熄灭与 breathing 插值怎么叠
  5. paintEventsrc/status_led.cpp:216)——自绘,径向渐变高光
  6. initBreathingAnimationsrc/status_led.cpp:62)——呼吸范式(QVariantAnimation LoopInfinite + InOutSine)

入口:demo/main.cppdemo/status_led_window.cpp 跑起来,对照读。

5. 踩坑

#现象原因后果解法
频繁切换状态偶发崩溃color_anim_DeleteWhenStopped,stop 后被 delete、指针悬空segfault持久成员指针 + stop()/重配/start(),不用 DeleteWhenStoppedsrc/status_led.cpp:55
绿→红过渡中间一帧偏暗褐Qt 对 QColor 默认 RGB 线性插值,绿红中间是橄榄色视觉略脏(非崩溃)OutCubic+300ms 掩盖;要鲜艳就注册 HSV 插值器(进阶挑战)
以为过渡时呼吸会让颜色乱跳误解:过渡和呼吸写在不同变量,正交过度设计去「冻结呼吸相位」认清 current_color_breathing_factor_ 解耦,合成即可并行
以为每帧 new QRadialGradient 有性能问题误判过度优化(缓存 gradient 反要处理失效)有意不缓存:LED 像素量极小,60fps 可忽略
动画回调里用了 repaint()repaint() 同步立即重绘,不等事件循环动画掉帧一律 update()(异步合并,src/status_led.cpp:117
窗口缩到极小时 LED 消失 / 不绘制半径 min(w,h)/2-1 在 w/h 极小时为负 / 0drawEllipse 行为未定义std::max(1, ...) 兜底(src/status_led.cpp:222
color 的 WRITE 错指向 setStatus动画驱动 setStatus → setStatus 又启动画 → 无限递归栈溢出WRITE 指 setAnimatedColor(纯赋值+emit+update),setStatus 是业务入口(src/status_led.cpp:111

6. 官方文档


这套机制(Q_PROPERTY + 动画驱动 + 解耦合成)不是 StatusLED 专属——它就是「一个可被动画驱动的自绘控件」的标准范式。toggle-switch 会换皮复用同一套骨架。想自己搓?手搓手册带你从空 main 一行行搓到这个成品。

基于 VitePress 构建