Qt环形进度条实现

引言

qt中自带进度条为直线,重写实现了环形进度条,效果如下
在这里插入图片描述

环形进度条

核心就是paintEvent+动画QPropertyAnimation实现,万物皆可paint,直接上代码吧

#include <QEvent>
#include <QProgressBar>

class CircularProgressDelegate;
class CircularProgress : public QProgressBar
{
    Q_OBJECT

    Q_PROPERTY(qreal lineWidth WRITE setLineWidth READ lineWidth)
    Q_PROPERTY(qreal size WRITE setSize READ size)
    Q_PROPERTY(QColor color WRITE setColor READ color)

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

    enum ProgressType {
        DeterminateProgress, // 普通进度条
        IndeterminateProgress // 等待进度条
    };

    void setProgressType(ProgressType type);
    CircularProgress::ProgressType progressType() const;

    void setUseThemeColors(bool value);
    bool useThemeColors() const;

    void setLineWidth(qreal width);
    qreal lineWidth() const;

    void setSize(int size);
    int size() const;

    void setColor(const QColor &color);
    QColor color() const;

    QSize sizeHint() const Q_DECL_OVERRIDE;

protected:
    void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;

private:
    void init();

private:
    CircularProgressDelegate * m_delegate;
    ProgressType m_progressType;
    QColor m_color;
    qreal m_penWidth;
    int m_size;
    bool m_useThemeColors;
};

class CircularProgressDelegate : public QObject
{
    Q_OBJECT

    Q_PROPERTY(qreal dashOffset WRITE setDashOffset READ dashOffset)
    Q_PROPERTY(qreal dashLength WRITE setDashLength READ dashLength)
    Q_PROPERTY(int angle WRITE setAngle READ angle)

public:
    CircularProgressDelegate(CircularProgress *parent);
    ~CircularProgressDelegate();

    void setDashOffset(qreal offset);
    qreal dashOffset() const;

    void setDashLength(qreal length);
    qreal dashLength() const;

    void setAngle(int angle);
    int angle() const;

private:
    Q_DISABLE_COPY(CircularProgressDelegate)

    CircularProgress *const m_progress;
    qreal m_dashOffset;
    qreal m_dashLength;
    int m_angle;
};
#include <QPropertyAnimation>
#include <QParallelAnimationGroup>
#include <QPainter>
#include <QHBoxLayout>
#include <QTimer>

CircularProgress::CircularProgress(QWidget *parent)
    : QProgressBar(parent)
{
    init();
}

CircularProgress::~CircularProgress()
{
}

void CircularProgress::setProgressType(ProgressType type)
{
    m_progressType = type;
    update();
}

CircularProgress::ProgressType CircularProgress::progressType() const
{
    return m_progressType;
}

void CircularProgress::setUseThemeColors(bool value)
{
    if (m_useThemeColors == value) {
        return;
    }

    m_useThemeColors = value;
    update();
}

bool CircularProgress::useThemeColors() const
{
    return m_useThemeColors;
}

void CircularProgress::setLineWidth(qreal width)
{
    m_penWidth = width;
    update();
    updateGeometry();
}

qreal CircularProgress::lineWidth() const
{
    return m_penWidth;
}

void CircularProgress::setSize(int size)
{
    m_size = size;
    update();
    updateGeometry();
}

int CircularProgress::size() const
{
    return m_size;
}

void CircularProgress::setColor(const QColor &color)
{
    m_color = color;
    update();
}

QColor CircularProgress::color() const
{
    if (m_useThemeColors || !m_color.isValid()) {
        return QColor(37,177,218);
    } else {
        return m_color;
    }
}

QSize CircularProgress::sizeHint() const
{
    const qreal s = m_size+m_penWidth+8;
    return QSize(s, s);
}

void CircularProgress::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event)

    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

    if (!isEnabled()){
        QPen pen;
        pen.setCapStyle(Qt::RoundCap);
        pen.setWidthF(m_penWidth);
        pen.setColor(QColor(186,186,186));
        painter.setPen(pen);
        painter.drawLine(rect().center()-QPointF(20, 20), rect().center()+QPointF(20, 20));
        painter.drawLine(rect().center()+QPointF(20, -20), rect().center()-QPointF(20, -20));
        return;
    }

    if (IndeterminateProgress == m_progressType){
        painter.translate(width()/2, height()/2);
        painter.rotate(m_delegate->angle());
    }

    QPen pen;
    pen.setCapStyle(Qt::RoundCap);
    pen.setWidthF(m_penWidth);

    // 绘制底部环
    pen.setColor(QColor(165,165,165));
    painter.setPen(pen);
    painter.drawEllipse(QPoint(0, 0), m_size / 2, m_size / 2);

    pen.setColor(color());

    if (IndeterminateProgress == m_progressType){
        QVector<qreal> pattern;
        pattern << m_delegate->dashLength()*m_size/50 << 30*m_size/50;

        pen.setDashOffset(m_delegate->dashOffset()*m_size / 50);
        pen.setDashPattern(pattern);

        painter.setPen(pen);

        painter.drawEllipse(QPoint(0, 0), m_size/2, m_size/2);
    }
    else
    {
        painter.setPen(pen);

        const qreal x = (width()-m_size)/2;
        const qreal y = (height()-m_size)/2;

        const qreal a = 360*(value()-minimum())/(maximum()-minimum());

        QPainterPath path;
        path.arcMoveTo(x, y, m_size, m_size, 0);
        path.arcTo(x, y, m_size, m_size, 0, a);

        painter.drawPath(path);
    }
}

void CircularProgress::init()
{
    m_delegate = new CircularProgressDelegate(this);
    m_progressType = IndeterminateProgress;
    m_penWidth = 6.25;
    m_size = 64;
    m_useThemeColors = true;

    setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding,
        QSizePolicy::MinimumExpanding));

    QParallelAnimationGroup *group = new QParallelAnimationGroup(this);
    group->setLoopCount(-1);

    QPropertyAnimation *animation;
    animation = new QPropertyAnimation(this);
    animation->setPropertyName("dashLength");
    animation->setTargetObject(m_delegate);
    animation->setEasingCurve(QEasingCurve::InOutQuad);
    animation->setStartValue(0.1);
    animation->setKeyValueAt(0.15, 0.2);
    animation->setKeyValueAt(0.6, 20);
    animation->setKeyValueAt(0.7, 20);
    animation->setEndValue(20);
    animation->setDuration(2050);

    group->addAnimation(animation);

    animation = new QPropertyAnimation(this);
    animation->setPropertyName("dashOffset");
    animation->setTargetObject(m_delegate);
    animation->setEasingCurve(QEasingCurve::InOutSine);
    animation->setStartValue(0);
    animation->setKeyValueAt(0.15, 0);
    animation->setKeyValueAt(0.6, -7);
    animation->setKeyValueAt(0.7, -7);
    animation->setEndValue(-25);
    animation->setDuration(2050);

    group->addAnimation(animation);

    animation = new QPropertyAnimation(this);
    animation->setPropertyName("angle");
    animation->setTargetObject(m_delegate);
    animation->setStartValue(0);
    animation->setEndValue(719);
    animation->setDuration(2050);

    group->addAnimation(animation);

    group->start();
}

/
CircularProgressDelegate::CircularProgressDelegate(CircularProgress *parent)
    : QObject(parent),
    m_progress(parent),
    m_dashOffset(0),
    m_dashLength(89),
    m_angle(0)
{
    Q_ASSERT(parent);
}

CircularProgressDelegate::~CircularProgressDelegate()
{
}

void CircularProgressDelegate::setDashOffset(qreal offset)
{
    m_dashOffset = offset;
    m_progress->update();
}

qreal CircularProgressDelegate::dashOffset() const
{
    return m_dashOffset;
}

void CircularProgressDelegate::setDashLength(qreal length)
{
    m_dashLength = length;
    m_progress->update();
}

qreal CircularProgressDelegate::dashLength() const
{
    return m_dashLength;
}

void CircularProgressDelegate::setAngle(int angle)
{
    m_angle = angle;
    m_progress->update();
}

int CircularProgressDelegate::angle() const
{
    return m_angle;
}

等待窗

实际项目中会大量使用到等待窗,大部分是用QDialog调用exec实现的,但这种方法会卡住整个主线程,软件不能操作其他的页签,用户体验不好。

换一种实现方式,通过raise只屏蔽部分空间,其他页签依旧可以操作的,代码如下

class WaitProgressWidget : public QWidget
{
    Q_OBJECT

public:
    WaitProgressWidget(QWidget *parent);
    ~WaitProgressWidget();
    void setBgColor(QColor color);

    void start(int msec = 60000);
    void stop(bool isOk);

signals:
    void sig_finished(bool isOk);

private:
    void show();// 限制外部调用show hide
    void hide();

protected:
    bool eventFilter(QObject *obj, QEvent *ev);
    void showEvent(QShowEvent *ev);

private:
    CircularProgress* m_progress;
    QWidget* m_parentWidget;
    QTimer* m_timer;
};
WaitProgressWidget::WaitProgressWidget(QWidget *parent)
    : QWidget(parent)
    , m_parentWidget(parent)
{
    m_progress = new CircularProgress(this);

    this->setAutoFillBackground(true);
    this->hide();

    QColor color(165,165,165);
    color.setAlphaF(0.6);
    this->setBgColor(color);

    auto waitBgLayout = new QHBoxLayout(this);
    waitBgLayout->addWidget(m_progress);
    waitBgLayout->setAlignment(m_progress, Qt::AlignCenter);

    parent->installEventFilter(this);

    m_timer = new QTimer(this);
    connect(m_timer, &QTimer::timeout, this, [this](){
        if(!this->isHidden()){
            stop(false);
        }
    });
}

WaitProgressWidget::~WaitProgressWidget()
{

}

void WaitProgressWidget::setBgColor(QColor color)
{
    QPalette palette;
    palette.setColor(QPalette::Background, color);
    this->setPalette(palette);
    update();
}

bool WaitProgressWidget::eventFilter(QObject *obj, QEvent *ev)
{
    if(obj == m_parentWidget && ev->type() == QEvent::Resize){
        if(!this->isHidden()){
            this->setFixedSize(m_parentWidget->size());
            this->raise();
        }
    }
    return QWidget::eventFilter(obj,ev);
}

void WaitProgressWidget::showEvent(QShowEvent *ev)
{
    Q_UNUSED(ev);

    this->setFixedSize(m_parentWidget->size());
    this->raise();
}

void WaitProgressWidget::start(int msecs)
{
    m_timer->start(msecs);
    this->show();
}

void WaitProgressWidget::stop(bool isOk)
{
    m_timer->stop();
    this->hide();
    emit sig_finished(isOk);
}

void WaitProgressWidget::show()
{
    QWidget::show();
}

void WaitProgressWidget::hide()
{
    QWidget::hide();
}
  • 5
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Arui丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值