查看完整版本: [-- Qt编写水波进度条控件 --]

QTCN开发网 -> Qt 作品展 -> Qt编写水波进度条控件 [打印本页] 登录 -> 注册 -> 回复主题 -> 发表主题

liudianwu 2017-08-24 13:13

Qt编写水波进度条控件

手机app中经常能见到各种各样的水波进度条之类的效果,觉得不错,用QWidget的QPainter绘制了一个。集成到了QUC自定义控件中。原理是利用正弦曲线产生平滑曲线点集合,然后用大路径减去当前进度路径,形成水波效果。
/**
* 水波进度条控件 作者:feiyangqingyun(QQ:517216493) 2017-8-23
* 1:可设置范围值,支持负数值
* 2:可设置水波密度,密度越大浪越多
* 3:可设置水波高度,高度越大越浪
* 4:可设置边框宽度
* 5:可设置是否显示进度值
* 6:可设置进度值是否为百分比格式
* 7:可设置背景颜色、边框颜色、当前进度颜色、文字颜色
* 8:提供三种样式风格选择 方形风格 圆形风格 椭圆风格
*/
[attachment=17736]
[attachment=17750]
[attachment=19372]
核心代码:
  1. void ProgressBarWater::drawValue(QPainter *painter)
    {
        int height = this-> height();
        int width = this->width();
        int side = qMin(width, height);

        //计算当前值所占百分比
        double percent = 1 - (double)(value - minValue) / (maxValue - minValue);

        //正弦曲线公式 y = A * sin(ωx + φ) + k

        //w表示周期,可以理解为水波的密度,值越大密度越大(浪越密集 ^_^),取值 密度*M_PI/宽度
        double w = waterDensity * M_PI / width;

        //A表示振幅,可以理解为水波的高度,值越大高度越高(越浪 ^_^),取值高度的百分比
        double A = height * waterHeight;

        //k表示y轴偏移,可以理解为进度,取值高度的进度百分比
        double k = height * percent;

        //第一条波浪路径集合
        QPainterPath waterPath1;
        //第二条波浪路径集合
        QPainterPath waterPath2;

        //移动到左上角起始点
        waterPath1.moveTo(0, height);
        waterPath2.moveTo(0, height);

        offset += 0.6;
        if (offset > (width / 2)) {
            offset = 0;
        }

        for(int x = 0; x <= width; x++) {
            //第一条波浪Y轴
            double waterY1 = (double)(A * sin(w * x + offset)) + k;

            //第二条波浪Y轴
            double waterY2 = (double)(A * sin(w * x + offset + (width / 2 * w))) + k;

            //如果当前值为最小值则Y轴为高度
            if (this->value == minValue) {
                waterY1 = height;
                waterY2 = height;
            }

            //如果当前值为最大值则Y轴为0
            if (this->value == maxValue) {
                waterY1 = 0;
                waterY2 = 0;
            }

            waterPath1.lineTo(x, waterY1);
            waterPath2.lineTo(x, waterY2);
        }

        //移动到右下角结束点,整体形成一个闭合路径
        waterPath1.lineTo(width, height);
        waterPath2.lineTo(width, height);

        //大路径
        QPainterPath bigPath;

        if (percentStyle == PercentStyle_Rect) {
            width = width - borderWidth * 2;
            height = height - borderWidth * 2;
            bigPath.addRect(borderWidth, borderWidth, width, height);
        } else if (percentStyle == PercentStyle_Circle) {
            side = side - borderWidth * 2;
            bigPath.addEllipse((width - side) / 2, borderWidth, side, side);
        } else if (percentStyle == PercentStyle_Ellipse) {
            width = width - borderWidth * 2;
            height = height - borderWidth * 2;
            bigPath.addEllipse(borderWidth, borderWidth, width, height);
        }

        painter->save();

        //新路径,用大路径减去波浪区域的路径,形成遮罩效果
        QPainterPath path;
        painter->setPen(Qt::NoPen);
        QColor waterColor1 = usedColor;
        waterColor1.setAlpha(100);
        QColor waterColor2 = usedColor;
        waterColor2.setAlpha(180);

        //第一条波浪挖去后的路径
        path = bigPath.intersected(waterPath1);
        painter->setBrush(waterColor1);
        painter->drawPath(path);

        //第二条波浪挖去后的路径
        path = bigPath.intersected(waterPath2);
        painter->setBrush(waterColor2);
        painter->drawPath(path);

        painter->restore();
    }







pengchengfan 2017-08-24 15:27
精神值得夸赞

王闯闯 2017-08-24 15:35
大师,厉害!
照着大师的代码简单实现了下

[attachment=17713]

michelle_hxy 2017-08-24 16:01

仗剑天涯 2017-08-24 18:16

t1029901995 2017-08-24 22:30
刘大师 一如既往的牛叉

liudianwu 2017-08-25 08:56
王闯闯:大师,厉害!
照着大师的代码简单实现了下
[图片]
 (2017-08-24 15:35) 

厉害,能够迅速的举一反三,不错,人才!

liuchangyin 2017-08-25 09:31

songhuirong1 2017-08-25 11:17

九重水 2017-08-25 14:40

fzw003 2017-08-25 17:25
可以给个参数范围吗?

liudianwu 2017-08-26 17:41
fzw003:可以给个参数范围吗? (2017-08-25 17:25) 

一般来说默认值是0-100

洗洗睡咯 2017-08-27 08:22
      

fzw003 2017-08-28 10:03
liudianwu:一般来说默认值是0-100 (2017-08-26 17:41) 

OK了很可爱的一个控件。

alex47451 2017-08-28 11:04
Mark

theotherone 2017-08-28 13:30
大神

核心科技 2017-08-29 21:26
牛!!!

llwj0303 2017-09-01 17:36
王闯闯:大师,厉害!
照着大师的代码简单实现了下
[图片]
 (2017-08-24 15:35) 

能不能分享下代码,那个offset是什么额

hunt978 2017-09-03 14:31
llwj0303:
能不能分享下代码,那个offset是什么额


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


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

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

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


代碼如下:
  1. /*******************************************************************************
    * filename : ProgressBarWater.hpp
    * create   : 2017-08-30 02:41:03 UTC
    * modified : 2017-08-30 03:16:48 UTC
    \******************************************************************************/

    #ifndef __PROGRESS_BAR_WATER_HPP__
    #define __PROGRESS_BAR_WATER_HPP__

    ////////////////////////////////////////////////////////////////////////////////
    // Headers
    //
    #include <QColor>
    #include <QWidget>
    #include <cstdint>

    ////////////////////////////////////////////////////////////////////////////////
    // Typedefs & Constants
    //

    ////////////////////////////////////////////////////////////////////////////////
    // Classes
    //
    class ProgressBarWater : public QWidget {
      Q_OBJECT

    public:
      explicit ProgressBarWater(QWidget* parent = nullptr);
      ~ProgressBarWater();

    public:
      using PercentStyle = enum {
        PercentStyle_Rect,
        PercentStyle_Circle,
        PercentStyle_Ellipse
      };

    protected:
      void drawValue(QPainter* painter);
      void paintEvent(QPaintEvent* event);

    private:
      uint32_t     value;
      uint32_t     minValue;
      uint32_t     maxValue;
      uint32_t     waterDensity;
      double       waterHeight;
      double       offset;
      PercentStyle percentStyle;
      uint32_t     borderWidth;
      QColor       usedColor;
    };
    ////////////////////////////////////////////////////////////////////////////////
    // Functions
    //

    #endif  // __PROGRESS_BAR_WATER_HPP__
    ////////////////////////////////// EOF /////////////////////////////////////////
  1. /*******************************************************************************
    * filename : ProgressBarWater.cpp
    * create   : 2017-08-30 02:41:03 UTC
    * modified : 2017-08-30 03:16:44 UTC
    \******************************************************************************/

    ////////////////////////////////////////////////////////////////////////////////
    // Headers
    //
    #include <QtWidgets>

    #include "ProgressBarWater.hpp"

    ////////////////////////////////////////////////////////////////////////////////
    // Typedefs & Constants
    //

    ////////////////////////////////////////////////////////////////////////////////
    // Inner Scope Objects
    //

    ////////////////////////////////////////////////////////////////////////////////
    // Classes
    //
    ProgressBarWater::ProgressBarWater(QWidget* parent) : QWidget(parent) {
      value        = 45;
      minValue     = 0;
      maxValue     = 100;
      waterDensity = 2;
      waterHeight  = 0.02;
      offset       = 0;
      percentStyle = PercentStyle_Rect;
      borderWidth  = 2;
      usedColor    = Qt::red;

      auto* timer = new QTimer(this);
      connect(timer, &QTimer::timeout, [=]() {
        offset += 0.6;
        update();
      });
      timer->start(100);
    }

    ProgressBarWater::~ProgressBarWater() {}

    void ProgressBarWater::paintEvent(QPaintEvent* event) {
      QPainter painter(this);
      painter.setRenderHint(QPainter::Antialiasing);
      drawValue(&painter);
    }

    void ProgressBarWater::drawValue(QPainter* painter) {
      int height = this->height();
      int width  = this->width();
      int side   = qMin(width, height);
      //计算当前值所占百分比
      double percent = 1 - (double)(value - minValue) / (maxValue - minValue);
      //正弦曲线公式 y = A * sin(ωx + φ) + k
      // w表示周期,可以理解为水波的密度,值越大密度越大(浪越密集 ^_^),取值
      // 密度*M_PI/宽度
      double w = waterDensity * M_PI / width;
      // A表示振幅,可以理解为水波的高度,值越大高度越高(越浪 ^_^),取值高度的百分比
      double A = height * waterHeight;
      // k表示y轴偏移,可以理解为进度,取值高度的进度百分比
      double k = height * percent;
      //第一条波浪路径集合
      QPainterPath waterPath1;
      //第二条波浪路径集合
      QPainterPath waterPath2;
      //移动到左上角起始点
      waterPath1.moveTo(0, height);
      waterPath2.moveTo(0, height);
      // offset += 0.6;
      if (offset > (width / 2)) { offset = 0; }
      for (int x = 0; x <= width; x++) {
        //第一条波浪Y轴
        double waterY1 = (double)(A * sin(w * x + offset)) + k;
        //第二条波浪Y轴
        double waterY2 = (double)(A * sin(w * x + offset + (width / 2 * w))) + k;
        //如果当前值为最小值则Y轴为高度
        if (this->value == minValue) {
          waterY1 = height;
          waterY2 = height;
        }
        //如果当前值为最大值则Y轴为0
        if (this->value == maxValue) {
          waterY1 = 0;
          waterY2 = 0;
        }
        waterPath1.lineTo(x, waterY1);
        waterPath2.lineTo(x, waterY2);
      }
      //移动到右下角结束点,整体形成一个闭合路径
      waterPath1.lineTo(width, height);
      waterPath2.lineTo(width, height);
      //大路径
      QPainterPath bigPath;
      if (percentStyle == PercentStyle_Rect) {
        width  = width - borderWidth * 2;
        height = height - borderWidth * 2;
        bigPath.addRect(borderWidth, borderWidth, width, height);
      } else if (percentStyle == PercentStyle_Circle) {
        side = side - borderWidth * 2;
        bigPath.addEllipse((width - side) / 2, borderWidth, side, side);
      } else if (percentStyle == PercentStyle_Ellipse) {
        width  = width - borderWidth * 2;
        height = height - borderWidth * 2;
        bigPath.addEllipse(borderWidth, borderWidth, width, height);
      }
      painter->save();
      //新路径,用大路径减去波浪区域的路径,形成遮罩效果
      QPainterPath path;
      painter->setPen(Qt::NoPen);
      QColor waterColor1 = usedColor;
      waterColor1.setAlpha(100);
      QColor waterColor2 = usedColor;
      waterColor2.setAlpha(180);
      //第一条波浪挖去后的路径
      path = bigPath.intersected(waterPath1);
      painter->setBrush(waterColor1);
      painter->drawPath(path);
      //第二条波浪挖去后的路径
      path = bigPath.intersected(waterPath2);
      painter->setBrush(waterColor2);
      painter->drawPath(path);
      painter->restore();
    }

    ////////////////////////////////////////////////////////////////////////////////
    // Functions
    //

    ////////////////////////////////// EOF /////////////////////////////////////////

  1. /*******************************************************************************
    * filename : main.cpp
    * create   : 2017-08-30 02:43:20 UTC
    * modified : 2017-08-30 03:16:50 UTC
    \******************************************************************************/

    ////////////////////////////////////////////////////////////////////////////////
    // Headers
    //
    #include <QApplication>

    #include "ProgressBarWater.hpp"

    ////////////////////////////////////////////////////////////////////////////////
    // Typedefs & Constants
    //

    ////////////////////////////////////////////////////////////////////////////////
    // Inner Scope Objects
    //

    ////////////////////////////////////////////////////////////////////////////////
    // Classes
    //

    ////////////////////////////////////////////////////////////////////////////////
    // Functions
    //
    int main(int argc, char* argv[]) {
      QApplication app(argc, argv);

      ProgressBarWater _ProgressBarWater;
      _ProgressBarWater.show();

      return app.exec();
    }

    ////////////////////////////////// EOF /////////////////////////////////////////





liudianwu 2017-09-03 16:21
以上代码会有问题,当类型为圆形,高度大于宽度时,变形严重。正确代码如下:
  1. void ProgressBarWater::drawValue(QPainter *painter)
    {
        int height = this-> height();
        int width = this->width();
        int side = qMin(width, height);

        //起始点坐标和结束点坐标
        int startX = borderWidth;
        int startY = borderWidth;
        int endX = width - borderWidth;
        int endY = height - borderWidth;

        //圆形的区域要重新计算
        if (percentStyle == PercentStyle_Circle) {
            side = side - borderWidth * 2;
            startX = (width - side) / 2;
            startY = (height - side) / 2;
            endX = (width + side) / 2;
            endY = (height + side) / 2;
        }

        //计算当前值所占百分比
        double percent = 1 - (double)(value - minValue) / (maxValue - minValue);

        //正弦曲线公式 y = A * sin(ωx + φ) + k

        //w表示周期,可以理解为水波的密度,值越大密度越大(浪越密集 ^_^),取值 密度*M_PI/宽度
        double w = waterDensity * M_PI / endX;

        //A表示振幅,可以理解为水波的高度,值越大高度越高(越浪 ^_^),取值高度的百分比
        double A = endY * waterHeight;

        //k表示y轴偏移,可以理解为进度,取值高度的进度百分比
        double k = endY * percent;

        //圆形的区域要重新计算
        if (percentStyle == PercentStyle_Circle) {
            k = (side * percent) + startY;
        }

        //第一条波浪路径集合
        QPainterPath waterPath1;
        //第二条波浪路径集合
        QPainterPath waterPath2;

        //移动到左上角起始点
        waterPath1.moveTo(startX, endY);
        waterPath2.moveTo(startX, endY);

        offset += 0.6;
        if (offset > (endX / 2)) {
            offset = 0;
        }

        for(int x = startX; x <= endX; x++) {
            //第一条波浪Y轴
            double waterY1 = (double)(A * sin(w * x + offset)) + k;
            //第二条波浪Y轴
            double waterY2 = (double)(A * sin(w * x + offset + (endX / 2 * w))) + k;

            //如果当前值为最小值则Y轴为右下角Y轴
            if (this->value == minValue) {
                waterY1 = endY;
                waterY2 = endY;
            }

            //如果当前值为最大值则Y轴为右上角Y轴
            if (this->value == maxValue) {
                waterY1 = startY;
                waterY2 = startY;
            }

            //将两个点连接成线
            waterPath1.lineTo(x, waterY1);
            waterPath2.lineTo(x, waterY2);
        }

        //移动到右下角结束点,整体形成一个闭合路径
        waterPath1.lineTo(endX, endY);
        waterPath2.lineTo(endX, endY);

        //大路径
        QPainterPath bigPath;
        if (percentStyle == PercentStyle_Rect) {
            bigPath.addRect(startX, startY, endX, endY);
        } else if (percentStyle == PercentStyle_Circle) {
            bigPath.addEllipse(startX, startY, side, side);
        } else if (percentStyle == PercentStyle_Ellipse) {
            bigPath.addEllipse(startX, startY, width - borderWidth * 2, height - borderWidth * 2);
        }

        //新路径,用大路径减去波浪区域的路径,形成遮罩效果
        QPainterPath path;
        QColor waterColor1 = usedColor;
        waterColor1.setAlpha(100);
        QColor waterColor2 = usedColor;
        waterColor2.setAlpha(200);

        painter->save();
        painter->setPen(Qt::NoPen);

        //第一条波浪挖去后的路径
        path = bigPath.intersected(waterPath1);
        painter->setBrush(waterColor1);
        painter->drawPath(path);

        //第二条波浪挖去后的路径
        path = bigPath.intersected(waterPath2);
        painter->setBrush(waterColor2);
        painter->drawPath(path);

        painter->restore();
    }


story2016 2017-09-03 16:37
厉害 原来思路是这样,不过上下幅值低些更美观

bcy11070601 2017-09-12 11:49
为什么不用QML做呢?

rekols 2017-09-13 17:42
bcy11070601:为什么不用QML做呢? (2017-09-12 11:49) 

QML适合移动设备。

王先生 2017-09-18 15:58

w642833823 2017-09-24 21:08
liudianwu: 大师,你的水波高度,密度控件怎么设置的,UI中只能设置0-100,而不能设置小数

哥依然潇洒 2017-09-25 15:13

liudianwu 2017-09-25 16:19
w642833823:
liudianwu: 大师,你的水波高度,密度控件怎么设置的,UI中只能设置0-100,而不能设置小数


能告诉我下图中有没有小数吗?
[attachment=17967]

yangliu8623 2017-11-29 18:04
牛逼了

w642833823 2017-12-02 18:20
谢谢,楼主,非常感谢你的答案,

ambitious 2018-01-24 14:26
大师,这个控件能否开源啊,正好需要,做电池充电用


查看完整版本: [-- Qt编写水波进度条控件 --] [-- top --]



Powered by phpwind v8.7 Code ©2003-2011 phpwind
Gzip disabled