• 13255阅读
  • 29回复

Qt编写水波进度条控件 [复制链接]

上一主题 下一主题
离线liudianwu
 

图酷模式  只看楼主 倒序阅读 楼主  发表于: 2017-08-24
— 本帖被 realfan 执行加亮操作(2017-08-26) —
手机app中经常能见到各种各样的水波进度条之类的效果,觉得不错,用QWidgetQPainter绘制了一个。集成到了QUC自定义控件中。原理是利用正弦曲线产生平滑曲线点集合,然后用大路径减去当前进度路径,形成水波效果。
/**
* 水波进度条控件 作者:feiyangqingyun(QQ:517216493) 2017-8-23
* 1:可设置范围值,支持负数值
* 2:可设置水波密度,密度越大浪越多
* 3:可设置水波高度,高度越大越浪
* 4:可设置边框宽度
* 5:可设置是否显示进度值
* 6:可设置进度值是否为百分比格式
* 7:可设置背景颜色、边框颜色、当前进度颜色、文字颜色
* 8:提供三种样式风格选择 方形风格 圆形风格 椭圆风格
*/



核心代码:
  1. void ProgressBarWater::drawValue(QPainter *painter)
  2. {
  3.     int height = this-> height();
  4.     int width = this->width();
  5.     int side = qMin(width, height);
  6.     //计算当前值所占百分比
  7.     double percent = 1 - (double)(value - minValue) / (maxValue - minValue);
  8.     //正弦曲线公式 y = A * sin(ωx + φ) + k
  9.     //w表示周期,可以理解为水波的密度,值越大密度越大(浪越密集 ^_^),取值 密度*M_PI/宽度
  10.     double w = waterDensity * M_PI / width;
  11.     //A表示振幅,可以理解为水波的高度,值越大高度越高(越浪 ^_^),取值高度的百分比
  12.     double A = height * waterHeight;
  13.     //k表示y轴偏移,可以理解为进度,取值高度的进度百分比
  14.     double k = height * percent;
  15.     //第一条波浪路径集合
  16.     QPainterPath waterPath1;
  17.     //第二条波浪路径集合
  18.     QPainterPath waterPath2;
  19.     //移动到左上角起始点
  20.     waterPath1.moveTo(0, height);
  21.     waterPath2.moveTo(0, height);
  22.     offset += 0.6;
  23.     if (offset > (width / 2)) {
  24.         offset = 0;
  25.     }
  26.     for(int x = 0; x <= width; x++) {
  27.         //第一条波浪Y轴
  28.         double waterY1 = (double)(A * sin(w * x + offset)) + k;
  29.         //第二条波浪Y轴
  30.         double waterY2 = (double)(A * sin(w * x + offset + (width / 2 * w))) + k;
  31.         //如果当前值为最小值则Y轴为高度
  32.         if (this->value == minValue) {
  33.             waterY1 = height;
  34.             waterY2 = height;
  35.         }
  36.         //如果当前值为最大值则Y轴为0
  37.         if (this->value == maxValue) {
  38.             waterY1 = 0;
  39.             waterY2 = 0;
  40.         }
  41.         waterPath1.lineTo(x, waterY1);
  42.         waterPath2.lineTo(x, waterY2);
  43.     }
  44.     //移动到右下角结束点,整体形成一个闭合路径
  45.     waterPath1.lineTo(width, height);
  46.     waterPath2.lineTo(width, height);
  47.     //大路径
  48.     QPainterPath bigPath;
  49.     if (percentStyle == PercentStyle_Rect) {
  50.         width = width - borderWidth * 2;
  51.         height = height - borderWidth * 2;
  52.         bigPath.addRect(borderWidth, borderWidth, width, height);
  53.     } else if (percentStyle == PercentStyle_Circle) {
  54.         side = side - borderWidth * 2;
  55.         bigPath.addEllipse((width - side) / 2, borderWidth, side, side);
  56.     } else if (percentStyle == PercentStyle_Ellipse) {
  57.         width = width - borderWidth * 2;
  58.         height = height - borderWidth * 2;
  59.         bigPath.addEllipse(borderWidth, borderWidth, width, height);
  60.     }
  61.     painter->save();
  62.     //新路径,用大路径减去波浪区域的路径,形成遮罩效果
  63.     QPainterPath path;
  64.     painter->setPen(Qt::NoPen);
  65.     QColor waterColor1 = usedColor;
  66.     waterColor1.setAlpha(100);
  67.     QColor waterColor2 = usedColor;
  68.     waterColor2.setAlpha(180);
  69.     //第一条波浪挖去后的路径
  70.     path = bigPath.intersected(waterPath1);
  71.     painter->setBrush(waterColor1);
  72.     painter->drawPath(path);
  73.     //第二条波浪挖去后的路径
  74.     path = bigPath.intersected(waterPath2);
  75.     painter->setBrush(waterColor2);
  76.     painter->drawPath(path);
  77.     painter->restore();
  78. }






欢迎关注微信公众号:Qt实战 (各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发)QQ:517216493  WX:feiyangqingyun  QQ群:751439350
离线pengchengfan

只看该作者 1楼 发表于: 2017-08-24
精神值得夸赞
离线王闯闯

只看该作者 2楼 发表于: 2017-08-24
大师,厉害!
照着大师的代码简单实现了下


离线michelle_hxy

只看该作者 3楼 发表于: 2017-08-24
离线仗剑天涯

只看该作者 4楼 发表于: 2017-08-24
离线t1029901995

只看该作者 5楼 发表于: 2017-08-24
刘大师 一如既往的牛叉
离线liudianwu

只看该作者 6楼 发表于: 2017-08-25
回 王闯闯 的帖子
王闯闯:大师,厉害!
照着大师的代码简单实现了下
[图片]
 (2017-08-24 15:35) 

厉害,能够迅速的举一反三,不错,人才!
欢迎关注微信公众号:Qt实战 (各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发)QQ:517216493  WX:feiyangqingyun  QQ群:751439350
离线liuchangyin

只看该作者 7楼 发表于: 2017-08-25
离线songhuirong1

只看该作者 8楼 发表于: 2017-08-25
离线九重水

只看该作者 9楼 发表于: 2017-08-25
离线fzw003

只看该作者 10楼 发表于: 2017-08-25
可以给个参数范围吗?
离线liudianwu

只看该作者 11楼 发表于: 2017-08-26
回 fzw003 的帖子
fzw003:可以给个参数范围吗? (2017-08-25 17:25) 

一般来说默认值是0-100
欢迎关注微信公众号:Qt实战 (各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发)QQ:517216493  WX:feiyangqingyun  QQ群:751439350
离线洗洗睡咯

只看该作者 12楼 发表于: 2017-08-27
      
离线fzw003

只看该作者 13楼 发表于: 2017-08-28
回 liudianwu 的帖子
liudianwu:一般来说默认值是0-100 (2017-08-26 17:41) 

OK了很可爱的一个控件。
离线alex47451

只看该作者 14楼 发表于: 2017-08-28
Mark
离线theotherone

只看该作者 15楼 发表于: 2017-08-28
大神
屌丝程序猿,努力更屌丝  
博客:http://fearlazy.com
离线核心科技

只看该作者 16楼 发表于: 2017-08-29
牛!!!
离线llwj0303

只看该作者 17楼 发表于: 2017-09-01
回 王闯闯 的帖子
王闯闯:大师,厉害!
照着大师的代码简单实现了下
[图片]
 (2017-08-24 15:35) 

能不能分享下代码,那个offset是什么额
专注C++,专注Qt
离线hunt978

只看该作者 18楼 发表于: 2017-09-03
回 llwj0303 的帖子
llwj0303:
能不能分享下代码,那个offset是什么额


參數解釋:
  1. using PercentStyle = enum {
  2. PercentStyle_Rect,  // 矩形
  3. PercentStyle_Circle, // 圓形
  4. PercentStyle_Ellipse // 橢圓
  5. };
  6. uint32_t     value; // 當前值
  7.   uint32_t     minValue; // 最小值,通常0
  8.   uint32_t     maxValue; //  最大值,通常100
  9.   uint32_t     waterDensity; // 波紋數量,最小1
  10.   double       waterHeight; // 波紋高度比例相對於控件高度,通常0.02
  11.   double       offset; // 波紋位置,控件自主維護的,初始值0
  12.   PercentStyle percentStyle; // 波紋類型
  13.   uint32_t     borderWidth; // 邊界寬度,可設置為3
  14.   QColor       usedColor; // 波紋顏色


如果要達成動態變化的,可以使用兩種方式,QTimer事件或者QProperyAnimation。以下是QTimer方式

  1. auto* timer = new QTimer(this);
  2.   connect(timer, &QTimer::timeout, [=]() {
  3.     offset += 0.6; // 原繪製方法在paintEvent時更新offset,但會導致控件大小變化等事件發生時導致波紋運動,故最好自主更新
  4.     update();
  5.   });
  6.   timer->start(100);

主繪製函數
  1. void ProgressBarWater::paintEvent(QPaintEvent* event) {
  2.   QPainter painter(this);
  3.   painter.setRenderHint(QPainter::Antialiasing);
  4.   drawValue(&painter);
  5. // \TODO 在此可以添加繪製邊框、繪製文字等等事件
  6. }


代碼如下:
  1. /*******************************************************************************
  2. * filename : ProgressBarWater.hpp
  3. * create   : 2017-08-30 02:41:03 UTC
  4. * modified : 2017-08-30 03:16:48 UTC
  5. \******************************************************************************/
  6. #ifndef __PROGRESS_BAR_WATER_HPP__
  7. #define __PROGRESS_BAR_WATER_HPP__
  8. ////////////////////////////////////////////////////////////////////////////////
  9. // Headers
  10. //
  11. #include <QColor>
  12. #include <QWidget>
  13. #include <cstdint>
  14. ////////////////////////////////////////////////////////////////////////////////
  15. // Typedefs & Constants
  16. //
  17. ////////////////////////////////////////////////////////////////////////////////
  18. // Classes
  19. //
  20. class ProgressBarWater : public QWidget {
  21.   Q_OBJECT
  22. public:
  23.   explicit ProgressBarWater(QWidget* parent = nullptr);
  24.   ~ProgressBarWater();
  25. public:
  26.   using PercentStyle = enum {
  27.     PercentStyle_Rect,
  28.     PercentStyle_Circle,
  29.     PercentStyle_Ellipse
  30.   };
  31. protected:
  32.   void drawValue(QPainter* painter);
  33.   void paintEvent(QPaintEvent* event);
  34. private:
  35.   uint32_t     value;
  36.   uint32_t     minValue;
  37.   uint32_t     maxValue;
  38.   uint32_t     waterDensity;
  39.   double       waterHeight;
  40.   double       offset;
  41.   PercentStyle percentStyle;
  42.   uint32_t     borderWidth;
  43.   QColor       usedColor;
  44. };
  45. ////////////////////////////////////////////////////////////////////////////////
  46. // Functions
  47. //
  48. #endif  // __PROGRESS_BAR_WATER_HPP__
  49. ////////////////////////////////// EOF /////////////////////////////////////////
  1. /*******************************************************************************
  2. * filename : ProgressBarWater.cpp
  3. * create   : 2017-08-30 02:41:03 UTC
  4. * modified : 2017-08-30 03:16:44 UTC
  5. \******************************************************************************/
  6. ////////////////////////////////////////////////////////////////////////////////
  7. // Headers
  8. //
  9. #include <QtWidgets>
  10. #include "ProgressBarWater.hpp"
  11. ////////////////////////////////////////////////////////////////////////////////
  12. // Typedefs & Constants
  13. //
  14. ////////////////////////////////////////////////////////////////////////////////
  15. // Inner Scope Objects
  16. //
  17. ////////////////////////////////////////////////////////////////////////////////
  18. // Classes
  19. //
  20. ProgressBarWater::ProgressBarWater(QWidget* parent) : QWidget(parent) {
  21.   value        = 45;
  22.   minValue     = 0;
  23.   maxValue     = 100;
  24.   waterDensity = 2;
  25.   waterHeight  = 0.02;
  26.   offset       = 0;
  27.   percentStyle = PercentStyle_Rect;
  28.   borderWidth  = 2;
  29.   usedColor    = Qt::red;
  30.   auto* timer = new QTimer(this);
  31.   connect(timer, &QTimer::timeout, [=]() {
  32.     offset += 0.6;
  33.     update();
  34.   });
  35.   timer->start(100);
  36. }
  37. ProgressBarWater::~ProgressBarWater() {}
  38. void ProgressBarWater::paintEvent(QPaintEvent* event) {
  39.   QPainter painter(this);
  40.   painter.setRenderHint(QPainter::Antialiasing);
  41.   drawValue(&painter);
  42. }
  43. void ProgressBarWater::drawValue(QPainter* painter) {
  44.   int height = this->height();
  45.   int width  = this->width();
  46.   int side   = qMin(width, height);
  47.   //计算当前值所占百分比
  48.   double percent = 1 - (double)(value - minValue) / (maxValue - minValue);
  49.   //正弦曲线公式 y = A * sin(ωx + φ) + k
  50.   // w表示周期,可以理解为水波的密度,值越大密度越大(浪越密集 ^_^),取值
  51.   // 密度*M_PI/宽度
  52.   double w = waterDensity * M_PI / width;
  53.   // A表示振幅,可以理解为水波的高度,值越大高度越高(越浪 ^_^),取值高度的百分比
  54.   double A = height * waterHeight;
  55.   // k表示y轴偏移,可以理解为进度,取值高度的进度百分比
  56.   double k = height * percent;
  57.   //第一条波浪路径集合
  58.   QPainterPath waterPath1;
  59.   //第二条波浪路径集合
  60.   QPainterPath waterPath2;
  61.   //移动到左上角起始点
  62.   waterPath1.moveTo(0, height);
  63.   waterPath2.moveTo(0, height);
  64.   // offset += 0.6;
  65.   if (offset > (width / 2)) { offset = 0; }
  66.   for (int x = 0; x <= width; x++) {
  67.     //第一条波浪Y轴
  68.     double waterY1 = (double)(A * sin(w * x + offset)) + k;
  69.     //第二条波浪Y轴
  70.     double waterY2 = (double)(A * sin(w * x + offset + (width / 2 * w))) + k;
  71.     //如果当前值为最小值则Y轴为高度
  72.     if (this->value == minValue) {
  73.       waterY1 = height;
  74.       waterY2 = height;
  75.     }
  76.     //如果当前值为最大值则Y轴为0
  77.     if (this->value == maxValue) {
  78.       waterY1 = 0;
  79.       waterY2 = 0;
  80.     }
  81.     waterPath1.lineTo(x, waterY1);
  82.     waterPath2.lineTo(x, waterY2);
  83.   }
  84.   //移动到右下角结束点,整体形成一个闭合路径
  85.   waterPath1.lineTo(width, height);
  86.   waterPath2.lineTo(width, height);
  87.   //大路径
  88.   QPainterPath bigPath;
  89.   if (percentStyle == PercentStyle_Rect) {
  90.     width  = width - borderWidth * 2;
  91.     height = height - borderWidth * 2;
  92.     bigPath.addRect(borderWidth, borderWidth, width, height);
  93.   } else if (percentStyle == PercentStyle_Circle) {
  94.     side = side - borderWidth * 2;
  95.     bigPath.addEllipse((width - side) / 2, borderWidth, side, side);
  96.   } else if (percentStyle == PercentStyle_Ellipse) {
  97.     width  = width - borderWidth * 2;
  98.     height = height - borderWidth * 2;
  99.     bigPath.addEllipse(borderWidth, borderWidth, width, height);
  100.   }
  101.   painter->save();
  102.   //新路径,用大路径减去波浪区域的路径,形成遮罩效果
  103.   QPainterPath path;
  104.   painter->setPen(Qt::NoPen);
  105.   QColor waterColor1 = usedColor;
  106.   waterColor1.setAlpha(100);
  107.   QColor waterColor2 = usedColor;
  108.   waterColor2.setAlpha(180);
  109.   //第一条波浪挖去后的路径
  110.   path = bigPath.intersected(waterPath1);
  111.   painter->setBrush(waterColor1);
  112.   painter->drawPath(path);
  113.   //第二条波浪挖去后的路径
  114.   path = bigPath.intersected(waterPath2);
  115.   painter->setBrush(waterColor2);
  116.   painter->drawPath(path);
  117.   painter->restore();
  118. }
  119. ////////////////////////////////////////////////////////////////////////////////
  120. // Functions
  121. //
  122. ////////////////////////////////// EOF /////////////////////////////////////////

  1. /*******************************************************************************
  2. * filename : main.cpp
  3. * create   : 2017-08-30 02:43:20 UTC
  4. * modified : 2017-08-30 03:16:50 UTC
  5. \******************************************************************************/
  6. ////////////////////////////////////////////////////////////////////////////////
  7. // Headers
  8. //
  9. #include <QApplication>
  10. #include "ProgressBarWater.hpp"
  11. ////////////////////////////////////////////////////////////////////////////////
  12. // Typedefs & Constants
  13. //
  14. ////////////////////////////////////////////////////////////////////////////////
  15. // Inner Scope Objects
  16. //
  17. ////////////////////////////////////////////////////////////////////////////////
  18. // Classes
  19. //
  20. ////////////////////////////////////////////////////////////////////////////////
  21. // Functions
  22. //
  23. int main(int argc, char* argv[]) {
  24.   QApplication app(argc, argv);
  25.   ProgressBarWater _ProgressBarWater;
  26.   _ProgressBarWater.show();
  27.   return app.exec();
  28. }
  29. ////////////////////////////////// EOF /////////////////////////////////////////




离线liudianwu

只看该作者 19楼 发表于: 2017-09-03
以上代码会有问题,当类型为圆形,高度大于宽度时,变形严重。正确代码如下:
  1. void ProgressBarWater::drawValue(QPainter *painter)
  2. {
  3.     int height = this-> height();
  4.     int width = this->width();
  5.     int side = qMin(width, height);
  6.     //起始点坐标和结束点坐标
  7.     int startX = borderWidth;
  8.     int startY = borderWidth;
  9.     int endX = width - borderWidth;
  10.     int endY = height - borderWidth;
  11.     //圆形的区域要重新计算
  12.     if (percentStyle == PercentStyle_Circle) {
  13.         side = side - borderWidth * 2;
  14.         startX = (width - side) / 2;
  15.         startY = (height - side) / 2;
  16.         endX = (width + side) / 2;
  17.         endY = (height + side) / 2;
  18.     }
  19.     //计算当前值所占百分比
  20.     double percent = 1 - (double)(value - minValue) / (maxValue - minValue);
  21.     //正弦曲线公式 y = A * sin(ωx + φ) + k
  22.     //w表示周期,可以理解为水波的密度,值越大密度越大(浪越密集 ^_^),取值 密度*M_PI/宽度
  23.     double w = waterDensity * M_PI / endX;
  24.     //A表示振幅,可以理解为水波的高度,值越大高度越高(越浪 ^_^),取值高度的百分比
  25.     double A = endY * waterHeight;
  26.     //k表示y轴偏移,可以理解为进度,取值高度的进度百分比
  27.     double k = endY * percent;
  28.     //圆形的区域要重新计算
  29.     if (percentStyle == PercentStyle_Circle) {
  30.         k = (side * percent) + startY;
  31.     }
  32.     //第一条波浪路径集合
  33.     QPainterPath waterPath1;
  34.     //第二条波浪路径集合
  35.     QPainterPath waterPath2;
  36.     //移动到左上角起始点
  37.     waterPath1.moveTo(startX, endY);
  38.     waterPath2.moveTo(startX, endY);
  39.     offset += 0.6;
  40.     if (offset > (endX / 2)) {
  41.         offset = 0;
  42.     }
  43.     for(int x = startX; x <= endX; x++) {
  44.         //第一条波浪Y轴
  45.         double waterY1 = (double)(A * sin(w * x + offset)) + k;
  46.         //第二条波浪Y轴
  47.         double waterY2 = (double)(A * sin(w * x + offset + (endX / 2 * w))) + k;
  48.         //如果当前值为最小值则Y轴为右下角Y轴
  49.         if (this->value == minValue) {
  50.             waterY1 = endY;
  51.             waterY2 = endY;
  52.         }
  53.         //如果当前值为最大值则Y轴为右上角Y轴
  54.         if (this->value == maxValue) {
  55.             waterY1 = startY;
  56.             waterY2 = startY;
  57.         }
  58.         //将两个点连接成线
  59.         waterPath1.lineTo(x, waterY1);
  60.         waterPath2.lineTo(x, waterY2);
  61.     }
  62.     //移动到右下角结束点,整体形成一个闭合路径
  63.     waterPath1.lineTo(endX, endY);
  64.     waterPath2.lineTo(endX, endY);
  65.     //大路径
  66.     QPainterPath bigPath;
  67.     if (percentStyle == PercentStyle_Rect) {
  68.         bigPath.addRect(startX, startY, endX, endY);
  69.     } else if (percentStyle == PercentStyle_Circle) {
  70.         bigPath.addEllipse(startX, startY, side, side);
  71.     } else if (percentStyle == PercentStyle_Ellipse) {
  72.         bigPath.addEllipse(startX, startY, width - borderWidth * 2, height - borderWidth * 2);
  73.     }
  74.     //新路径,用大路径减去波浪区域的路径,形成遮罩效果
  75.     QPainterPath path;
  76.     QColor waterColor1 = usedColor;
  77.     waterColor1.setAlpha(100);
  78.     QColor waterColor2 = usedColor;
  79.     waterColor2.setAlpha(200);
  80.     painter->save();
  81.     painter->setPen(Qt::NoPen);
  82.     //第一条波浪挖去后的路径
  83.     path = bigPath.intersected(waterPath1);
  84.     painter->setBrush(waterColor1);
  85.     painter->drawPath(path);
  86.     //第二条波浪挖去后的路径
  87.     path = bigPath.intersected(waterPath2);
  88.     painter->setBrush(waterColor2);
  89.     painter->drawPath(path);
  90.     painter->restore();
  91. }

欢迎关注微信公众号:Qt实战 (各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发)QQ:517216493  WX:feiyangqingyun  QQ群:751439350
离线story2016

只看该作者 20楼 发表于: 2017-09-03
厉害 原来思路是这样,不过上下幅值低些更美观
离线bcy11070601

只看该作者 21楼 发表于: 2017-09-12
为什么不用QML做呢?
离线rekols

只看该作者 22楼 发表于: 2017-09-13
回 bcy11070601 的帖子
bcy11070601:为什么不用QML做呢? (2017-09-12 11:49) 

QML适合移动设备。
离线王先生

只看该作者 23楼 发表于: 2017-09-18
离线w642833823

只看该作者 24楼 发表于: 2017-09-24
liudianwu: 大师,你的水波高度,密度控件怎么设置的,UI中只能设置0-100,而不能设置小数

只看该作者 25楼 发表于: 2017-09-25
离线liudianwu

只看该作者 26楼 发表于: 2017-09-25
回 w642833823 的帖子
w642833823:
liudianwu: 大师,你的水波高度,密度控件怎么设置的,UI中只能设置0-100,而不能设置小数


能告诉我下图中有没有小数吗?
欢迎关注微信公众号:Qt实战 (各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发)QQ:517216493  WX:feiyangqingyun  QQ群:751439350
离线yangliu8623

只看该作者 27楼 发表于: 2017-11-29
牛逼了
离线w642833823

只看该作者 28楼 发表于: 2017-12-02
谢谢,楼主,非常感谢你的答案,
离线ambitious

只看该作者 29楼 发表于: 2018-01-24
大师,这个控件能否开源啊,正好需要,做电池充电用
快速回复
限100 字节
 
上一个 下一个