一个软件是否“高级”,往往不是看界面有多漂亮,而是看页面切换是否自然。
很多 Qt 项目都有多个页面,例如:登录页 → 主界面、首页 → 参数配置、参数配置 → 历史记录、设备列表 → 设备详情。遗憾的是,大部分 Qt 项目都是这样切换的
ui->stackedWidget->setCurrentIndex(1);运行效果:第一页瞬间消失,第二页突然出现,整个页面像“闪现”一样,没有任何过渡。而真正优秀的软件,比如 Qt Creator、WPS、腾讯会议、微信 PC 版,页面切换都带着细腻的动画:淡入淡出、左右滑动、缩放、甚至组合效果。这些动画不仅提升美观,更重要的是告诉用户:页面正在发生变化,而不是凭空跳转。
今天我们就用 Qt 实现一套企业级页面切换动画,让你的 QStackedWidget 瞬间拥有高级感。
一、为什么推荐 QStackedWidget?
Qt 官方提供的 QStackedWidget 是管理多页面的标准容器,结构非常清晰:
MainWindow└── QStackedWidget├── LoginPage├── HomePage├── SettingPage└── AboutPage
ui->stackedWidget->setCurrentWidget(ui->pageHome);二、动画的原理:截图法 vs 属性法
给 QStackedWidget 加动画有两种常用思路:
| 截图法 | |||
| 属性法 | geometry/pos/opacity 等属性做动画,最后一帧完成切换 |
实际项目推荐:截图法用于复杂布局的过渡,属性法用于简单容器动画。本文将重点介绍属性法,配合 QGraphicsOpacityEffect 实现所有效果。
三、准备工作:为页面创建透明度效果
Qt 的 QWidget 本身没有 opacity 属性,但我们可以借助 QGraphicsOpacityEffect:
auto *effect = new QGraphicsOpacityEffect(page);page->setGraphicsEffect(effect);effect->setOpacity(1.0); // 1.0 完全不透明,0.0 完全透明
之后对 effect 的 opacity 属性做动画即可控制页面显隐。
四、完整 Demo 1:Fade 淡入淡出
这是企业项目最常见的页面动画。效果:旧页面逐渐透明消失,新页面逐渐显现。
核心代码:AnimationHelper 类
// AnimationHelper.h#include<QWidget>#include<QPropertyAnimation>#include<QGraphicsOpacityEffect>#include<QParallelAnimationGroup>class AnimationHelper {public:// 淡入淡出切换页面staticvoidfadeSwitch(QWidget *oldPage, QWidget *newPage,int duration = 250,QEasingCurve curve = QEasingCurve::OutCubic);};
// AnimationHelper.cpp#include"AnimationHelper.h"#include<QGraphicsOpacityEffect>#include<QPropertyAnimation>#include<QParallelAnimationGroup>voidAnimationHelper::fadeSwitch(QWidget *oldPage, QWidget *newPage,int duration, QEasingCurve curve) {// 为新页面创建透明度效果auto *newEffect = new QGraphicsOpacityEffect(newPage);newEffect->setOpacity(0.0);newPage->setGraphicsEffect(newEffect);// 如果旧页面有图形效果就复用,否则新建auto *oldEffect = qobject_cast<QGraphicsOpacityEffect*>(oldPage->graphicsEffect());if (!oldEffect) {oldEffect = new QGraphicsOpacityEffect(oldPage);oldEffect->setOpacity(1.0);oldPage->setGraphicsEffect(oldEffect);}// 旧页面淡出auto *fadeOut = new QPropertyAnimation(oldEffect, "opacity");fadeOut->setDuration(duration);fadeOut->setStartValue(1.0);fadeOut->setEndValue(0.0);fadeOut->setEasingCurve(curve);// 新页面淡入auto *fadeIn = new QPropertyAnimation(newEffect, "opacity");fadeIn->setDuration(duration);fadeIn->setStartValue(0.0);fadeIn->setEndValue(1.0);fadeIn->setEasingCurve(curve);// 并行执行auto *group = new QParallelAnimationGroup;group->addAnimation(fadeOut);group->addAnimation(fadeIn);// 动画开始前先将新页面显示并置于顶层newPage->show();newPage->raise();QObject::connect(group, &QParallelAnimationGroup::finished, [oldPage, newPage]() {// 动画结束后确保最终状态auto *oldEff = qobject_cast<QGraphicsOpacityEffect*>(oldPage->graphicsEffect());if (oldEff) oldEff->setOpacity(1.0);auto *newEff = qobject_cast<QGraphicsOpacityEffect*>(newPage->graphicsEffect());if (newEff) newEff->setOpacity(1.0);oldPage->hide(); // 隐藏旧页面,避免鼠标穿透});group->start(QAbstractAnimation::DeleteWhenStopped);}
使用示例
// 从登录页切换到主页AnimationHelper::fadeSwitch(ui->loginPage, ui->homePage, 250, QEasingCurve::OutCubic);
五、完整 Demo 2:Slide 左右滑动
滑动效果像手机 App 一样,页面从屏幕右侧滑入,旧页面向左滑出。
voidAnimationHelper::slideSwitch(QWidget *oldPage, QWidget *newPage,bool rightToLeft, int duration,QEasingCurve curve) {QWidget *parent = oldPage->parentWidget();int width = parent ? parent->width() : oldPage->width();// 新页面初始位置int startX = rightToLeft ? width : -width;int endX = 0;newPage->move(startX, 0);newPage->show();newPage->raise();auto *oldAnim = new QPropertyAnimation(oldPage, "pos");oldAnim->setDuration(duration);oldAnim->setStartValue(QPoint(0, 0));oldAnim->setEndValue(QPoint(rightToLeft ? -width : width, 0));oldAnim->setEasingCurve(curve);auto *newAnim = new QPropertyAnimation(newPage, "pos");newAnim->setDuration(duration);newAnim->setStartValue(QPoint(startX, 0));newAnim->setEndValue(QPoint(endX, 0));newAnim->setEasingCurve(curve);auto *group = new QParallelAnimationGroup;group->addAnimation(oldAnim);group->addAnimation(newAnim);QObject::connect(group, &QParallelAnimationGroup::finished, [oldPage, newPage]() {oldPage->hide();oldPage->move(0, 0);newPage->move(0, 0);});group->start(QAbstractAnimation::DeleteWhenStopped);}
使用:AnimationHelper::slideSwitch(ui->homePage, ui->settingPage, true, 300);右滑返回:AnimationHelper::slideSwitch(ui->settingPage, ui->homePage, false, 300);
六、完整 Demo 3:Zoom 缩放切换
新页面从中心从小变大,旧页面同时淡出或缩小消失。
voidAnimationHelper::zoomSwitch(QWidget *oldPage, QWidget *newPage,int duration, QEasingCurve curve) {QWidget *parent = oldPage->parentWidget();QRect finalGeo = parent ? parent->rect() : oldPage->geometry();QRect startGeo(finalGeo.center().x() - 50, finalGeo.center().y() - 30, 100, 60);newPage->setGeometry(startGeo);newPage->show();newPage->raise();auto *geoAnim = new QPropertyAnimation(newPage, "geometry");geoAnim->setDuration(duration);geoAnim->setStartValue(startGeo);geoAnim->setEndValue(finalGeo);geoAnim->setEasingCurve(curve);// 同时旧页面淡出auto *oldEffect = new QGraphicsOpacityEffect(oldPage);oldEffect->setOpacity(1.0);oldPage->setGraphicsEffect(oldEffect);auto *fadeAnim = new QPropertyAnimation(oldEffect, "opacity");fadeAnim->setDuration(duration);fadeAnim->setStartValue(1.0);fadeAnim->setEndValue(0.0);fadeAnim->setEasingCurve(curve);auto *group = new QParallelAnimationGroup;group->addAnimation(geoAnim);group->addAnimation(fadeAnim);QObject::connect(group, &QParallelAnimationGroup::finished, [oldPage, newPage]() {oldPage->hide();auto *eff = qobject_cast<QGraphicsOpacityEffect*>(oldPage->graphicsEffect());if (eff) eff->setOpacity(1.0);newPage->setGeometry(finalGeo);});group->start(QAbstractAnimation::DeleteWhenStopped);}
七、完整 Demo 4:Fade + Slide 组合(企业级最爱)
同时滑动和淡入淡出,视觉流畅且高级。
voidAnimationHelper::fadeSlideSwitch(QWidget *oldPage, QWidget *newPage,bool rightToLeft, int duration) {QWidget *parent = oldPage->parentWidget();int width = parent ? parent->width() : oldPage->width();int startX = rightToLeft ? width : -width;newPage->move(startX, 0);newPage->show();newPage->raise();// 新页面透明度效果auto *newEffect = new QGraphicsOpacityEffect(newPage);newEffect->setOpacity(0.0);newPage->setGraphicsEffect(newEffect);// 旧页面透明度效果auto *oldEffect = new QGraphicsOpacityEffect(oldPage);oldEffect->setOpacity(1.0);oldPage->setGraphicsEffect(oldEffect);// 旧页面:滑出 + 淡出auto *oldMove = new QPropertyAnimation(oldPage, "pos");oldMove->setDuration(duration);oldMove->setStartValue(QPoint(0, 0));oldMove->setEndValue(QPoint(rightToLeft ? -width/2 : width/2, 0));auto *oldFade = new QPropertyAnimation(oldEffect, "opacity");oldFade->setDuration(duration);oldFade->setStartValue(1.0);oldFade->setEndValue(0.0);// 新页面:滑入 + 淡入auto *newMove = new QPropertyAnimation(newPage, "pos");newMove->setDuration(duration);newMove->setStartValue(QPoint(startX, 0));newMove->setEndValue(QPoint(0, 0));auto *newFade = new QPropertyAnimation(newEffect, "opacity");newFade->setDuration(duration);newFade->setStartValue(0.0);newFade->setEndValue(1.0);auto *group = new QParallelAnimationGroup;group->addAnimation(oldMove);group->addAnimation(oldFade);group->addAnimation(newMove);group->addAnimation(newFade);QObject::connect(group, &QParallelAnimationGroup::finished, [oldPage, newPage, oldEffect, newEffect]() {oldPage->hide();oldPage->move(0, 0);newPage->move(0, 0);if (oldEffect) oldEffect->setOpacity(1.0);if (newEffect) newEffect->setOpacity(1.0);});group->start(QAbstractAnimation::DeleteWhenStopped);}
八、企业项目实践:统一动画管理器
将上面所有静态方法放入 AnimationHelper 类中,以后项目中任何地方都可以一行调用:
// 淡入淡出AnimationHelper::fadeSwitch(currentPage, newPage);// 滑动AnimationHelper::slideSwitch(currentPage, newPage, true);// 缩放AnimationHelper::zoomSwitch(currentPage, newPage);// 滑动+淡入AnimationHelper::fadeSlideSwitch(currentPage, newPage, true);
更进一步的封装:我们可以创建一个 PageManager 类,继承 QStackedWidget,重写 setCurrentIndex,内部自动添加动画,这样业务代码完全不需要改动。
九、动画参数推荐表
为了避免动画风格不统一,建议全项目采用以下参数:
OutCubic | ||
OutCubic | ||
OutBack | ||
InOutCubic | ||
OutExpo |
统一后,整个软件交互会显得非常专业、一致。
十、总结
页面切换动画是提升 Qt 应用质感最有效的方式之一。今天我们讲解了:
截图法与属性法的优劣
四种常用动画的完整实现(Fade、Slide、Zoom、组合)
企业级管理器的封装思路
推荐参数表和避坑指南
你可以直接将文中的 AnimationHelper 类复制到项目中,再配合 QStackedWidget,立刻拥有媲美 WPS 的切换体验。
夜雨聆风