第二十章:定时器与动画——让界面“活”起来
说实话,Qt 的界面如果只是静态的按钮和文本框,那跟一张图片没什么区别。真正让用户觉得“这软件做得不错”的,往往是那些细腻的动画和恰到好处的定时反馈。这一章,我们就来聊聊 Qt 里跟时间、动画打交道的几个核心类:QTimer、QTimeLine、QPropertyAnimation、QParallelAnimationGroup,还有缓动曲线。
我个人习惯把定时器和动画比作“心跳”和“舞步”。定时器是稳定的心跳,每隔一段时间触发一次;动画则是精心编排的舞步,让属性值平滑变化。两者结合,界面就有了生命力。
从最基础的 QTimer 定时触发,到 QTimeLine 控制时间线,再到 QPropertyAnimation 驱动属性动画,最后用 QParallelAnimationGroup 并行播放多个动画,配合缓动曲线让运动更自然。
20.1 QTimer:最基础的定时器
QTimer 是 Qt 里最常用的定时器类。说白了,它就是让你在指定时间间隔后执行一段代码。我刚开始学 Qt 时,第一个练手的小项目就是用 QTimer 做了一个倒计时器。
QTimer 有两种工作模式:
- 单次触发:调用
singleShot(),只执行一次。适合延迟操作。 - 周期触发:调用
start(),每隔 interval 毫秒触发一次 timeout 信号。
// 单次触发:2秒后执行
QTimer::singleShot(2000, this, [=]() {
qDebug() << "2秒到了!";
});
// 周期触发:每1秒触发一次
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, [=]() {
qDebug() << "滴答...";
});
timer->start(1000); // 1000毫秒 = 1秒
嗯,这里要注意:定时器的精度受系统影响。Windows 上默认精度约 15 毫秒,如果你需要高精度定时(比如 1 毫秒),得用 QElapsedTimer 或者平台相关的 API。不过一般 UI 动画用不到那么高精度。
20.2 QTimeLine:控制时间线的利器
你想想看,如果我想让一个进度条从 0 走到 100,但中间要分 50 步走完,每步走多少?这时候 QTimer 就有点麻烦了——你得自己算步长。而 QTimeLine 就是专门干这个的。
QTimeLine 把一段时间分成若干帧,每帧触发一个 frameChanged 信号。你只需要告诉它总时长和总帧数,它自动帮你算好每一步。
QTimeLine *timeline = new QTimeLine(3000, this); // 总时长3秒
timeline->setFrameRange(0, 100); // 帧范围0~100
timeline->setUpdateInterval(50); // 每50毫秒更新一次
connect(timeline, &QTimeLine::frameChanged, this, [=](int frame) {
ui->progressBar->setValue(frame);
});
timeline->start();
我在项目中遇到过用 QTimeLine 做轮播图切换的场景。每帧切换透明度,配合缓动曲线,效果非常平滑。比用 QTimer 手动算步长省心多了。
finished 信号里又调用了 start(),结果造成了无限循环。后来我加了一个标志位来控制是否允许重新开始。记住:finished 信号触发后,时间线就停了,想循环得手动处理。
20.3 QPropertyAnimation:属性动画的核心
如果说 QTimeLine 是“手动挡”,那 QPropertyAnimation 就是“自动挡”。你只需要告诉它:哪个对象的哪个属性,从什么值变到什么值,多长时间。剩下的它全包了。
举个例子,让一个按钮在 2 秒内从位置 (0,0) 移动到 (200,0):
QPushButton *btn = new QPushButton("移动", this);
btn->setGeometry(0, 0, 80, 30);
QPropertyAnimation *anim = new QPropertyAnimation(btn, "pos");
anim->setDuration(2000); // 2秒
anim->setStartValue(QPoint(0, 0));
anim->setEndValue(QPoint(200, 0));
anim->setEasingCurve(QEasingCurve::OutBounce); // 缓动曲线
anim->start();
你看,代码就这么几行。按钮就会从左边弹到右边,还带点“弹跳”效果。这就是缓动曲线的功劳。
QPropertyAnimation 支持所有 Qt 属性系统里的属性,比如 geometry、windowOpacity、styleSheet 里的颜色值等等。但注意:属性必须注册到 Qt 属性系统(有 Q_PROPERTY 宏)。
- 动画操作的是 属性,不是直接操作坐标或颜色
- 属性必须有对应的 setter 和 getter
- 属性类型必须是 Qt 支持的 插值类型(int、float、QPoint、QColor 等)
20.4 QParallelAnimationGroup:让动画一起跑
有时候我们需要多个动画同时播放。比如一个窗口淡出的同时,还要缩小。这时候 QParallelAnimationGroup 就派上用场了。
它就像一个“动画容器”,把多个 QPropertyAnimation 放进去,然后统一 start(),所有子动画就会同时开始。
QPropertyAnimation *fadeAnim = new QPropertyAnimation(window, "windowOpacity");
fadeAnim->setDuration(1000);
fadeAnim->setStartValue(1.0);
fadeAnim->setEndValue(0.0);
QPropertyAnimation *scaleAnim = new QPropertyAnimation(window, "geometry");
scaleAnim->setDuration(1000);
scaleAnim->setStartValue(window->geometry());
scaleAnim->setEndValue(QRect(center.x(), center.y(), 0, 0));
QParallelAnimationGroup *group = new QParallelAnimationGroup(this);
group->addAnimation(fadeAnim);
group->addAnimation(scaleAnim);
group->start();
我个人习惯用 QParallelAnimationGroup 来做页面切换效果。比如从列表页切换到详情页,列表页淡出、详情页淡入,同时背景有个缩放效果。三个动画并行,用户体验非常流畅。
QSequentialAnimationGroup。它和 QParallelAnimationGroup 是兄弟类,用法一样,只是执行顺序不同。
20.5 缓动曲线:让动画告别“机械感”
为什么有些动画看起来很生硬?因为它是匀速的。现实世界中的运动很少有匀速的——你扔一个球,它先加速再减速,落地还会弹几下。缓动曲线就是模拟这种自然运动的。
Qt 提供了 QEasingCurve 类,内置了 40 多种缓动曲线。常用的有:
| 曲线类型 | 效果描述 | 适用场景 |
|---|---|---|
| Linear | 匀速运动 | 进度条、滚动条 |
| InQuad | 先慢后快 | 物体掉落 |
| OutQuad | 先快后慢 | 弹窗出现 |
| InOutQuad | 两头慢中间快 | 页面滑动 |
| OutBounce | 末尾弹跳 | 按钮点击反馈 |
| OutElastic | 末尾弹性拉伸 | 特殊强调效果 |
// 设置缓动曲线
anim->setEasingCurve(QEasingCurve::OutCubic);
// 也可以自定义曲线(通过设置控制点)
QEasingCurve customCurve(QEasingCurve::BezierSpline);
customCurve.addCubicBezierSegment(QPointF(0.2, 0.8), QPointF(0.8, 0.2), QPointF(1.0, 1.0));
anim->setEasingCurve(customCurve);
我曾经在做一个消息提示框时,用了 OutElastic 曲线。提示框从顶部弹下来时,会像果冻一样晃两下再停住。用户反馈说“这个动画好有趣”。你看,一个小小的缓动曲线,就能让用户记住你的软件。
OutBounce 和 OutElastic 这类曲线会超出终点范围,如果你的属性有边界限制(比如透明度不能超过 1.0),记得做好 clamp。
20.6 综合示例:一个带缓动的淡入淡出切换
最后,我把这一章的知识点串起来,写一个完整的例子。假设我们有两个页面(QWidget),点击按钮切换,切换时旧页面淡出、新页面淡入。
void switchPage(QWidget *oldPage, QWidget *newPage) {
// 旧页面淡出
QPropertyAnimation *fadeOut = new QPropertyAnimation(oldPage, "windowOpacity");
fadeOut->setDuration(500);
fadeOut->setStartValue(1.0);
fadeOut->setEndValue(0.0);
fadeOut->setEasingCurve(QEasingCurve::OutQuad);
// 新页面淡入
QPropertyAnimation *fadeIn = new QPropertyAnimation(newPage, "windowOpacity");
fadeIn->setDuration(500);
fadeIn->setStartValue(0.0);
fadeIn->setEndValue(1.0);
fadeIn->setEasingCurve(QEasingCurve::InQuad);
// 并行播放
QParallelAnimationGroup *group = new QParallelAnimationGroup();
group->addAnimation(fadeOut);
group->addAnimation(fadeIn);
// 动画结束后清理
connect(group, &QParallelAnimationGroup::finished, group, &QObject::deleteLater);
newPage->show();
group->start();
}
这个例子虽然简单,但涵盖了 QPropertyAnimation、缓动曲线、QParallelAnimationGroup 三个核心知识点。你想想看,如果不用动画,直接 hide() 和 show(),用户会觉得页面切换很突兀。加了这 500 毫秒的淡入淡出,感觉就完全不一样了。
- QTimer:定时触发,适合周期性任务或延迟操作
- QTimeLine:按帧控制,适合需要精确步进的场景
- QPropertyAnimation:属性动画,自动插值,最常用
- QParallelAnimationGroup:并行动画组,多个动画同时播放
- 缓动曲线:让动画更自然,告别生硬
嗯,这一章的内容就到这里。定时器和动画是 Qt 界面开发中非常出彩的部分,掌握了它们,你的软件就能从“能用”变成“好用”。下次写界面时,不妨试试给按钮加个弹跳效果,或者给窗口加个淡入动画——相信我,用户会感受到你的用心。
公众号:蓝海资料掘金营,微信deep3321