正常
Step 3:平滑旋转动画(value/needleAngle 解耦——核心)
这步是整个控件的核心——把 step 2 的「突变指针」升级成「400ms 平滑旋转」。诀窍和 status-led / toggle-switch 一脉相承:把「当前指针角度」做成一个 Q_PROPERTY,让 QPropertyAnimation 每帧去写它,而 value 只当业务入口。
目标
加 Q_PROPERTY(qreal needleAngle ...),setValue 时启动一个 QPropertyAnimation 把 needleAngle 从当前角度接力到 angleForValue(新value),指针平滑旋转。Slider 连拖 / 狂点不跳变、不崩。
提示(按顺序)
- 把 needleAngle 声明成 Q_PROPERTY:
Q_PROPERTY(qreal needleAngle READ needleAngle WRITE setNeedleAngle NOTIFY needleAngleChanged),补needleAngleChanged信号 setNeedleAngle(qreal)作为 WRITE 回调——这是动画每帧调的:纯赋值needle_angle_ = angle+emit needleAngleChanged+update()。只赋值,不启动画。用qFuzzyCompare去重防抖(相邻帧角度几乎相同就不重绘)- 改 setValue:不再直接设
needle_angle_,而是needle_anim_->stop()setStartValue(needle_angle_)(从当前显示角度接力,不是从上次的目标角度)setEndValue(angleForValue(value))start()
- needle_anim_ 用持久成员指针:构造时
new QPropertyAnimation(this, "needleAngle", this)(parent=this 托管),配setDuration(400)+setEasingCurve(QEasingCurve::OutCubic),不要用DeleteWhenStopped——频繁切换时指针悬空崩 - value 也做成 Q_PROPERTY(READ/WRITE/NOTIFY),但 value 的 WRITE 指 setValue(业务入口);needleAngle 的 WRITE 指 setNeedleAngle(纯赋值)——别搞反
关键认知
- value 与 needleAngle 解耦的根因:如果 needleAngle 的 WRITE 指向 setValue,动画每帧驱动 setValue → setValue 又启动画 → 无限递归栈溢出。所以 WRITE 必须指那个纯赋值的回调(setNeedleAngle),setValue 只是业务入口算映射后启动画。这套和 status-led 的 setAnimatedColor 同构,troubleshooting 有专门一节讲这个崩溃。
- 从 needle_angle_ 接力而非从目标角度重启——快速连切(Slider 拖、Random Jump 狂点)时指针从它此刻停的位置直接转到新目标,不会先闪回旧目标。这就是 demo 里 Random Jump 狂点看着顺滑的原因。
- 持久指针 vs DeleteWhenStopped:DeleteWhenStopped 在 stop 时 delete 对象,成员指针就悬空了;持久指针 + stop()/重配/start() 复用同一对象,没有 new/delete 抖动、不悬空。
检查点
setValue(180)后指针 400ms 平滑旋转到 180(不是瞬移)= 动画对了- 用 QSlider 连续拖:指针连贯追踪、不卡顿不跳变 = 接力逻辑对了
- Random Jump 狂点:指针平滑改向、不闪回旧目标 = setStartValue 取当前显示值对了
- 快速反复 setValue 不崩 = 持久指针对了(没用 DeleteWhenStopped)
搓到这,SpeedMeter 主体就齐了。想再深一层(指针危险区渐红 / 高转速刻度分段配色 / 过冲回弹)回 手册首页 看进阶挑战。
动画框架不熟?属性动画框架基础、进阶 动画框架进阶。Q_PROPERTY 机制不熟?QObject 与元对象系统、进阶 属性系统深度拆解。
对照答案
- needleAngle 的 Q_PROPERTY(WRITE 指 setNeedleAngle):
include/speed_meter.h:31 - needle_anim_ 初始化(持久指针 + parent=this + duration/easing):
src/speed_meter.cpp:54-58 - setValue 启动过渡(stop/setStart(needle_angle_)/setEnd/start):
src/speed_meter.cpp:74-86 - setNeedleAngle 回调(qFuzzyCompare 去重 + 赋值 + emit + update):
src/speed_meter.cpp:95-101
搓完了。跑 demo 对照成品:静态几档 + Cycle 接力 + Slider 追踪 + Random Jump 狂点都能复现 = 你搓的和 repo 一致。完整 demo 见 demo/speed_meter_window.cpp 四组布局。