一、简述

最近接到一个新需求,让做一个动效进度条。

由于我们的产品比较大,在软件启动的时候会消耗比较长的时间,原生的进度条已经不能满足我们的需求,这里我们就需要一个会动的进度条,效果如下图所示。

光效进度条主要是做了一个进度动画,在已完成的部分上进行快速的迭代渲染,给用户一种直观感受,我们的软件一直努力加载,它还活着。

有了这个进度条之后,当我们的进度从40%到50%这个持续的过程中,界面再也不会出现假死的情况,是不是很完美呢。。。

下面我就来分析下这个动效进度条是怎么做的

二、动效进度条

如效果图所示,光效进度条不同于一般的进度条,他在基础的任务进度之上还添加了一层光效,主要是想告诉用户我们的软件一直在努力运行,请在耐心的等待一下下。

我自己在做功能的时候,往往喜欢先做一个测试demo,然后在把做好的功能集成在正式环境,这个功能也不列外,如第一节中展示的效果图,就是测试demo的样子,虽然很丑,但是基础功能是有的。

现在的很多软件,在进度展示上都有了比较绚丽的效果,比如压缩软件,解压文件的时候都会有动效进度条,用过的同学应该都知道长啥样,而我们的光效进度条跟这个效果差不多,除此之外我们还提供了另一种动效,延迟动效,他们两个在一定程度上都展示了更友好的进度效果。

在开始分析功能前,首先我们先来考虑下我们的需求:动效进度条,也就是说在原来的进度条基础上需要添加实时动画,让进度条看起来更炫酷一些,除了光效进度条以外,还有一种延迟到达进度条,也属于动态进度条。

延迟动效、说直白一点儿就是延迟到达,当我们设置了进度从10%到20%时,程序模拟了一个渐进的过程,使用一个时间段走完了这10%的进度。

下面我们分别来介绍这两种进度条的实现

实现炫酷的进度条我们可以从QWidget自定义开始写,也就是说从头开始写,但通常我们不这样干,因为这样可能会写出无穷无尽的bug,而且现有的轮子已经很稳定了,为什么还要造呢。

1、光效进度条

光效进度条我们使用了一个小技巧,采用一个简单的办法实现,我们的光效进度条控件继承自Qt原生进度条类QProgressBar,在新类中我们只需要在Qt绘制完原生进度条之后,补画动效即可。

a、paintEvent函数

paintEvent函数是Qt的绘制函数,当界面刷新的时候,这个接口函数就会被调用,因此我们需要重写这个接口,首先调用父窗口的绘制方法,然后我们在绘制我们自己的动效,代码如下所示

  1. QProgressBar::paintEvent(event);
  2. drawCache绘制动效

b、drawCache绘制动效

绘制动效的时候,我们需要知道动效的绘制区域,这个地方我们需要主动去解析qss的一些参数,Qt的style()->subElementRect这个接口刚好可以拿到我们需要的信息

下面简单描述下我们的实现流程

  • 首先我们获取进度条的几何大小和中间进度的几何大小,这样的话我们就可以计算出来各border的数值
  • 然后根据我们当前的value值就可以计算出进度条已经走过(就是值小于我们设定的区域)的几何大小
  • 我们的光效将是跑在第二步计算出来的区域上,一直循环迭代
  • 内存里我们维护一个cacheValue,这个值在每次界面刷新的时候递增,但是不能大于第二步的value值,cacheValue将是我们动效绘制的一个关键参数,他表示了动效绘制的长度
  • 构造一个渐变刷子,设置给QPainter
  • 绘制动效

上下大致描述了下绘制动效的一个流程,下面送上具体代码,由于篇幅原因,代码我进行了部分伪代码处理。

  1. void GMPProgressBar::drawCache()
  2. {
  3. QStyleOptionProgressBarV2 opt;
  4. QRect outerRect = style()->subElementRect(QStyle::SE_ProgressBarGroove, &opt, this);
  5. QRect innerRect = style()->subElementRect(QStyle::SE_ProgressBarContents, &opt, this);
  6. QMargins borders(构造一个QMargins);
  7. QRectF rect(动效绘制区域);
  8. if (m_iCacheValue != 0)
  9. {
  10. QPainter painter(this);
  11. QLinearGradient gradient(构造绘图刷子);
  12. painter.setBrush(gradient);
  13. painter.drawRoundedRect(rect, 2, 2);
  14. }
  15. }

c、定时刷新

由于我们的动效是需要主动去刷新的,因此我们需要声明一个定时器,然后定时去刷新,实现代码可能像下面这样

  1. connect(m_pCacheTimer, &QTimer::timeout, this, [this]{
  2. if (TM_CACHE == m_mode)
  3. {
  4. ++m_iCacheValue;
  5. repaint();
  6. }else
  7. {
  8. m_pCacheTimer->stop();
  9. }
  10. });

定时器只需要在我们第一次设置进度条值的时候启动,或者当我们设置一个新的值,而定时器没有启动,我们就需要去激活定时器。

TM_CACHE模式即是我们的动效模式,TM_SMOOTH模式则是我们的延迟到达模式

  1. connect(this, &QProgressBar::valueChanged, [this](int value){//TM_CACHE模式下 启动动画时机
  2. if (!m_pCacheTimer->isActive() && value != 0 && TM_CACHE == m_mode)
  3. {
  4. m_pCacheTimer->start(m_iRefreshleveling);
  5. }
  6. });

动效进度条效果如下图所示

2、延迟到达进度条

动效进度条可能更适用于启动界面,但是也有一些时候,我们可能需要更平缓的一个加载曲线,例如安装软件、卸载软件的时候。

a、setValue

延迟到达进度条和动效进度条的实现方式就有所差别了,对于实现延迟到达进度条,我们这里重写了setValue函数,当外部调用该接口设置value值时,我们并没有立即去设置当前值,而是使用了一个时间段去完成这个值得刷新。

  • 外部调用setValue时,我们首先计算出我们应该绘制的最大宽度PixelMax、当前已经绘制到的最大宽度cacheValue和我们的步长
  • 设置定时器刷新频率,并重启定时器
  • 定时器刷新时,cacheValue自增我们的步长
  • 调用父类的QProgressBar::setValue接口设置值

b、定时器

延迟达到功能的的定时器和之前我们什么的动效定时器可以混用一个,我们定时器刷新的时候,针对不同的动画模式,我们执行不同的的代码即可,实现代码可能像下面这样

  1. connect(m_pCacheTimer, &QTimer::timeout, this, [this]{
  2. if (TM_CACHE == m_mode)
  3. {
  4. ++m_iCacheValue;
  5. repaint();
  6. }
  7. else if (TM_SMOOTH == m_mode)
  8. {
  9. changeSmooth();
  10. }
  11. else
  12. {
  13. m_pCacheTimer->stop();
  14. }
  15. });

延迟到达进度条效果如下图所示

3、接口说明

光效进度条类对外只暴露了3个接口,分别是设置动画模式、动画时长和刷新频率

特别需要注意的是,我们这里重写了父类的setValue接口,这意味着我们不能使用多态来操作这个接口

  1. void setTransitionMode(TransitionMode mode);//设置动画模式
  2. void setSmoothDuration(int duration);//设置刷新总时长 模式为TM_SMOOTH时有效
  3. void setRefreshleveling(int rate);//设置刷新频率 每次更改TransitionMode之后会变为默认值

a、修改动画模式

修改动画模式的时候,我们需要清空内存中的所有数据,并把value值设为0。

  1. void GMPProgressBar::setTransitionMode(TransitionMode mode)
  2. {
  3. if (m_mode == mode)
  4. {
  5. return;
  6. }
  7. m_mode = mode;
  8. clearData();
  9. QProgressBar::setValue(0);
  10. }

b、其他接口

设置刷新时长和频率接口都比较简单,不做特别说明

特别注意:这个3个接口最好是在动画启动前设置,动画开始后尽量不要去调用

三、启动图

第二节我们主要是讲述了怎么做一个动效进度条,这一节我们来做一个启动图页面,把这个动效使用进去。

启动图不是我们主要分析的内容,这个我就简单说下这个类的实现方式和一些借口说明

1、实现思路

Qt已经给我们提供了一个QSplashScreen,但是使用起来还是特别有限,因此这里我把Qt的源码直接进行了二次开发

  • 首先Qt的原生实现方式基本都被移植了出来
  • 启动图使用了简单的上下布局,上面是一张我自绘制的图片,放在了一个QLabel上,下面是动效进度条
  • 自绘制的图片上包括了,产品名称、logo、背景图等等

2、背景图切换

当我们调用setPixmap设置背景图时,如果我们指定了多张图,我将会启动一个定时器,在指定时长后重新构造一张大的背景图,并添加到启动窗口上

这里主要说明下背景图是怎么构造出来的,代码如下所示

a、根据背景图构造启动图大小,并移动到屏幕中间

  1. m_currentPixmap = m_lstPixmaps.at(m_iCurrentIndex);
  2. QRect size(QPoint(), m_currentPixmap.size() / m_currentPixmap.devicePixelRatio());
  3. size.setHeight(size.height() + StatusBarHeight);
  4. setFixedSize(size.size());
  5. m_pProgressBar->setFixedWidth(size.width() / 8 * 3);
  6. move(QApplication::desktop()->screenGeometry().center() - size.center());

b、绘制程序logo

  1. QPainter painter(&m_currentPixmap);
  2. painter.drawPixmap(m_startPos, m_logo);

c、绘制标题栏

  1. painter.save();
  2. painter.setFont(m_titleFont);
  3. QFontMetrics fontMetrics(m_titleFont);
  4. int textWidth = fontMetrics.width(m_strWindowTitle);
  5. int textHegith = m_logo.height();
  6. QRect textTect = QRect(m_startPos + QPoint(13 + m_logo.width(), 0), QSize(textWidth, textHegith));
  7. painter.drawText(textTect, m_strWindowTitle, QTextOption(Qt::AlignCenter));
  8. painter.restore();

d、设置给QLabel背景图

  1. m_pWindowBackground->setPixmap(m_currentPixmap);

启动图的效果这里就不在贴图了,第三节上的两个gif图都是最终的启动图效果

四、测试

最后就是测试代码了,主要是模拟了程序的一个加载过程

1、构造启动图

首先我们构造一个启动图对象,并设置程序logo和动画模式

  1. GMPSplashScreen * screen = new GMPSplashScreen(QPixmap(":/splashScreen/start.png"));
  2. screen->show();
  3. screen->setLogo(QIcon("logo.ico").pixmap(48, 48));
  4. screen->setTransitionMode(GMPProgressBar::TM_CACHE);

2、背景图

设置背景图,并设置背景图更换时间间隔

  1. QList<QPixmap> lstPixmap;
  2. lstPixmap.append(QPixmap(":/splashScreen/start.png"));
  3. lstPixmap.append(QPixmap(":/splashScreen/start.jpg"));
  4. screen->setPixmap(lstPixmap, 2000);

3、其他信息

设置程序的提示信息和标题栏

  1. screen->showMessage("Established connections", 0);
  2. screen->setTitle(QStringLiteral("广联达BIM土建计量GTJ2018"));

4、事件循环

这里写了一个死循环,主要是为了模拟程序的一个加载过程,每隔10ms处理下界面刷新事件

  1. a.processEvents();
  2. while (1)
  3. {
  4. QTest::qSleep(10);
  5. a.processEvents();
  6. }
  7. splashScreen w;
  8. w.show();
  9. screen->finish(&w);

五、源码

需要源码的留邮箱,现在的csdn简直太坑爹了。。。

如果您觉得文章不错,不妨给个打赏,写作不易,感谢各位的支持。您的支持是我最大的动力,谢谢!!!

很重要--转载声明

  1. 本站文章无特别说明,皆为原创,版权所有,转载时请用链接的方式,给出原文出处。同时写上原作者:朝十晚八 or Twowords

  2. 如要转载,请原文转载,如在转载时修改本文,请事先告知,谢绝在转载时通过修改本文达到有利于转载者的目的。


Qt实现炫酷启动图-动态进度条的更多相关文章

  1. 纯CSS炫酷3D旋转立方体进度条特效

    在网站制作中,提高用户体验度是一项非常重要的任务.一个创意设计不但能吸引用户的眼球,还能大大的提高用户的体验.在这篇文章中,我们将大胆的将前面所学的3D立方体和进度条结合起来,制作一款纯CSS3的3D ...

  2. 一款非常炫酷的jQuery动态随机背景滚动特效

    一款非常炫酷的jQuery动态随机背景滚动特效 图片背景会不停息的滚动,带有那种漂浮的视觉效果,小圈圈飘动. 更好的是还兼容IE6浏览器,大伙可以好好研究研究. 适用浏览器:IE6.IE7.IE8.3 ...

  3. 简直要逆天!超炫的 HTML5 粒子效果进度条

    我喜欢粒子效果作品,特别是那些能够应用于实际的,例如这个由 Jack Rugile 基于 HTML5 Cavnas 编写的进度条效果.看着这么炫的 Loading 效果,即使让我多等一会也无妨:)你呢 ...

  4. bootstrap得动态进度条

    Bootstrap的动态进度条: html: 创建一个modal   这里使用fade先将modal隐藏起来,然后modal里面嵌入progress 代码很简单 <div class=" ...

  5. 超炫的HTML5粒子效果进度条 VS 如何规范而优雅地code

    最近瞎逛的时候发现了一个超炫的粒子进度效果,有多炫呢?请擦亮眼镜!   // _this.ch){ _this.particles.splice(i, 1); } }; this.Particle.p ...

  6. 超简单CSS3水平动态进度条+小圆球+背景色渐变

    实现的的效果图如下:效果是动态加载的 代码如下: <!DOCTYPE html> <html lang="en"> <head> <met ...

  7. bootstrap与jQuery结合的动态进度条

    此款进度条实现的功能: 1.利用了bootstrap的进度条组件. a.在最外层的<div>中加入class .progress,在里层<div>加入class .progre ...

  8. Vue实现mp3音乐播放及动态进度条

    今天碰到一个Vue点击mp3播放及进度条动态走动的小功能,记录一下: 首先是通过HTML5 audio标签引入音频: <template> <div class="x-fo ...

  9. 利用面向对象思想封装Konva动态进度条

    1.html代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...

随机推荐

  1. 构建自己的PHP框架(路由)

    完整项目地址:https://github.com/Evai/Aier 上一篇中我们已经建立了一个空的 Composer 项目,本篇将讲述如何构建路由. 下面我们就开始自己来构建路由,先去 GitHu ...

  2. 操作系统hosts文件

    为了便于北京和大连两个更好的测试系统.该公司专门申请一个域名:大连r \\ u0026 D侧只需要部署(我方系统全权负责在大连研发.所以在大连并列比较的部署方面easy--不要忘记,该项目比我们实际做 ...

  3. WPF RelativeSource的使用

    <Window x:Class="XamlTest.Window15"        xmlns="http://schemas.microsoft.com/win ...

  4. WM_SIZE后于WM_CREATE消息!!在窗口被创建时的顺序!

    WM_SIZE   procedure WMSize (var Message: TWMSize); message WM_SIZE; 参数说明 wParam: Specifies the type ...

  5. Apache Cordova for ios环境配置

    原文:Apache Cordova for ios环境配置 1.安装针对iOS的工具 https://technet.microsoft.com/ZH-cn/library/dn757054.aspx ...

  6. 基于Spring开发

    1. XML Schema 1.1 最简单的标签 一个最简单的标签,形式如: <bf:head-routing key="1" value="1" to= ...

  7. Android零基础入门第59节:AnalogClock、DigitalClock和TextClock时钟组件

    原文:Android零基础入门第59节:AnalogClock.DigitalClock和TextClock时钟组件 在前面一期,我们学习了DatePicker和TimePicker,在实际开发中其不 ...

  8. C#跳转语句

    1.break 退出直接封闭它的switch.while.do.for或foreach语句. 当有嵌套时,break只退出最里层的语句块. break不能跳出finally语句块. 2.continu ...

  9. MySQL操作详解

    创建并使用数据库 查看服务器上的数据库:SHOW DATABASES; 创建数据库:CREATE DATABASE <数据库名>; 指明使用何数据库:USE <数据库名> 创建 ...

  10. Python中的参数传递问题

    首先需要说明python中元组,列表,字典的区别. 列表: 什么是列表呢?我觉得列表就是我们日常生活中经常见到的清单. 例如:lst = ['arwen',123] 向list中添加项有两种方法:ap ...