引言
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();
}