欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

QT 带有动画的 圆形进度条 水波进度条

程序员文章站 2024-02-02 11:21:46
...

我们在使用其他软件的时候,经常能看到各种绚丽的进度条,其中带有水波波纹的进度条就是其中一个。对于PC端软件开发使用的QT,只是提供了process bar,样式十分单一。 所以这次我们就使用QT的paint 以及定时器来实现类似的水波进度条。
先看一下最终效果图:
QT 带有动画的 圆形进度条 水波进度条

这个是怎样实现的呢? 我们仍然是在一个QWidget上的paintEvent上重绘实现图形绘制 + QTimer 实现动画效果。

首先,我们是在一个QWidget 上进行绘制,绘制是在paintEvent 里面进行开始绘制。水波动画是使用QTimer 定时器进行定时触,重绘控件,造成水波波动的动画。
我们照例来分解一下整个实现过程: 圆形背景(边框+背景) + 水波进度 + 进度数值。
第一步,我们先绘制圆形的背景,这个背景包含边框,以及未被占有的背景。这个背景是一个圆形,我们使用 drawEllipse函数:

void QPainter::drawEllipse(const QRectF &rectangle)

其中我们通过设置 画笔QPen 的宽度来实现 边框:

QPen pen;
pen.setColor(borderColor);
pen.setWidthF(borderWidth);
pen.setJoinStyle(Qt::RoundJoin);
painter->setPen(pen);

通过设置画刷 QBrush 来设置未占用的的背景色:

painter->setBrush(QBrush(m_bgColor));

这样就完成了第一步,背景的绘制:

void WaterProcess::drawBg(QPainter *painter)
{
    int width = this->width();
    int height = this->height();
    int side = qMin(width, height) - m_borderWidth;

    int startX = (width - side) * 0.5;
    int startY = (height - side) * 0.5;

    painter->save();
    painter->setBrush(QBrush(m_bgColor));
    if (m_borderWidth == 0) {
        painter->setPen(Qt::NoPen);
    } else {
        QBrush brush(m_borderColor);
        painter->setPen(QPen(brush, m_borderWidth, Qt::SolidLine));
    }

    painter->drawEllipse(startX, startY, side, side);
    painter->restore();
}

我们来看看效果:
QT 带有动画的 圆形进度条 水波进度条

第二步,是需要绘制水波,这也是至关重要的一步。我们使用设定的颜色使用QPainterPath来设定,其中最重要的有两个,第一个就是QPainterPath 的一个intersected(求相交、挖除)功能。

QPainterPath QPainterPath::intersected(const QPainterPath &p) const

这个功能是返回两个路径相交(重合)的部分。我们来分析一下为什么使用这个求相交的功能。
因为我们要实现水波,所以我们需要使用我们以前学过的正弦sin 或者余弦cos 函数,进行模拟水波。

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

  • A : 表示振幅, 我们可以用作水波的高度。
  • w : 表示周期,可以理解为水波密度,W越大,表示水波越密集。
  • k : 标识Y轴偏移量,可以理解为进度,取值是进度百分比

我们可以设置两个透明度不同、起始位置有偏差的两条正弦曲线相互交错,形成波浪的起伏,更具有真实性。
我们的大路径 totalPainterPath,是整个圆形。上面用到的正弦公式模拟的水波。我们就要使用到了上面提到的intersected相交函数,来去除额外不需要显示的部分。

void WaterProcess::drawProcess(QPainter *painter)
{
    int width = this->width();
    int height = this->height();
    int side = qMin(width, height) - (2 * m_borderWidth); //直径

    int startX = (width - side) * 0.5;
    int startY = (height - side) *0.5;
    int endX = startX + side;
    int endY = startY + side;

    double percent = (m_value * 1.0) / (m_maxValue - m_minValue);

    double w = 2 * M_PI / endX;
    double A = endY * m_waterHeight;
    double k = endY * (1.0 - percent);

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

    QPainterPath totalPath;
    //加入圆形路径
    totalPath.addEllipse(startX, startY, side, side);

    //水波路径
    QPainterPath water1;
    QPainterPath water2;

    water1.moveTo(startX, endY);
    water2.moveTo(startX, endY);

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

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

        water1.lineTo(i, waterY1);
        water2.lineTo(i, waterY2);

        if (m_value == m_minValue) {
            waterY1 = endY;
        }

        if (m_value == m_maxValue) {
            waterY1 = startY;
        }
    }
    //封闭
    water1.lineTo(endX, endY);
    water2.lineTo(endX, endY);

    QPainterPath path;
    QColor waterColor1 = m_usedColor;
    waterColor1.setAlpha(100);
    QColor waterColor2 = m_usedColor;
    waterColor2.setAlpha(200);

    //第一条波浪
    path = totalPath.intersected(water1);
    painter->setBrush(waterColor1);
    painter->drawPath(path);

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


    painter->restore();
}

这个时候我们看一下效果图:
QT 带有动画的 圆形进度条 水波进度条
这时候就已经完成了大部分功能,不过很重要的是没有显示进度百分比。

第三步就是 完成文字百分比的显示了,我们使用drawText函数进行函数绘制。

void QPainter::drawText(const QPointF &position, const QString &text)

这个就比较简单了,只需要算出来百分比就可以了,其实上一步我们已经完成了对百分比的算法了

void WaterProcess::drawValue(QPainter *painter)
{
    painter->save();
    int width = this->width();
    int height = this->height();
    int side = qMin(width, height) - m_borderWidth;

    int startX = (width - side) * 0.5;
    int startY = (height - side) * 0.5;

    int fontSize = side / 3;


    QFont font;
    font.setFamily("微软雅黑");
    font.setPixelSize(fontSize);
    font.setBold(true);

    painter->setFont(font);
    painter->setPen(Qt::white);
    painter->drawText(QRectF(startX, startY, side, side), Qt::AlignCenter, QString("%1%").arg(m_value));

    painter->restore();
}

到这里我们就完成了所有动作的绘制,只需要在paintEvent函数里进行调用了

void WaterProcess::paintEvent(QPaintEvent *ev)
{
    Q_UNUSED(ev)

    QPainter painter(this);
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);

    //背景
    drawBg(&painter);

    //进度、水波
    drawProcess(&painter);

    //进度数字
    drawValue(&painter);
}

在初始化的时候启动定时器就好了!

写在最后,例子已经上传到csdn了,没有积分的小伙伴,评论留下你的邮箱,看到后会立刻发出。

demo下载地址