如何在Qt中繪制一個弧形的進度條
在圖形用戶界面開發中,進度指示控件(Progress Widget)是非常常見且實用的組件。CCArcProgressWidget
是一個繼承自 QWidget
的自定義控件,用于繪制圓弧形進度條。當然,筆者看了眼公開的實現,基本上都非常完善了,筆者在這里添加了一個更好的動畫。
類定義概覽
CCArcProgressWidget
類定義在 CCArcProgressWidget.h
中,使用 Qt 元對象系統,通過 Q_OBJECT
宏啟用信號與屬性機制。該控件支持如下屬性綁定:
value
:當前進度值maxValue
:最大進度值displayValue
:動畫過程中的顯示值(與實際value
異步)
這些屬性可被 QML 或動畫機制綁定,便于動態效果的呈現。(筆者這里使用的是Q_PROPERTY屬性系統公開的,所以QML可用(笑))
靜態常量定義(這部分是筆者認為編譯的時候可以指定的)
類中定義了多個靜態常量,用于控制組件的外觀與行為:
DURATION
:動畫持續時間(單位:毫秒),默認為 500msARC_WIDTH
:圓弧的線寬,默認為 50 像素DEFAULT_VALUE
:默認初始值,為 0DEFAULT_MAX
:默認最大值,為 100DEF_TEXT_COLOR
:默認文本顏色DEF_BKCOLOR
:默認背景弧顏色(未完成部分)DEF_ARC_COLOR
:默認進度弧顏色(已完成部分)
這些常量使得控件具有清晰的默認狀態,便于使用和維護。
屬性訪問與設置接口
該類提供了一系列 inline
內聯函數和公開接口,用于讀取與設置進度值及外觀樣式:
int value() const
:獲取當前進度值void setValue(int val)
:設置當前進度值(含動畫)int maxValue() const
:獲取最大值void setMaxValue(int max)
:設置最大值QColor progressArcColor() const
/void setProgressArcColor(const QColor&)
:讀取與設置進度弧顏色QColor progressBackgroundColor() const
/void setProgressBackgroundColor(const QColor&)
:讀取與設置背景弧顏色QColor progressTextColor() const
/void setProgressTextColor(const QColor&)
:讀取與設置文本顏色
所有設置函數內部均會判斷是否真正發生變化,避免無謂的刷新,若發生更改則調用 update()
觸發重繪。
信號機制
該控件定義了三個信號:
valueChanged(int)
:當用戶設置新進度值時發出maxValueChanged(int)
:當最大值被重新設置時發出displayValueChanged(int)
:當動畫中顯示的值發生變化時發出
這些信號便于其他模塊(如界面展示、數據記錄)實時響應進度的變化。
繪制函數與動畫支持
該類重載了 paintEvent
事件處理函數,實現核心繪制邏輯。繪制內容包括三部分:
- 背景弧:通過
drawBackgroundArc()
繪制未完成部分 - 進度弧:通過
drawProgressArc()
根據當前動畫角度繪制完成部分 - 中心文本:通過
drawText()
繪制當前數值或狀態文字
同時,setupAnimation()
函數用于構建 QPropertyAnimation
動畫,使 value
到 displayValue
之間具備平滑過渡效果。動畫期間實際值不變,僅 displayValue
動態變化,從而提升用戶體驗。
私有成員變量
類中使用了如下私有成員保存狀態:
progress_value
:當前進度值progress_display_value
:當前顯示值(用于動畫)progress_max_value
:最大進度值progress_minAngle
與progress_startAngle
:控制弧線的起始與方向(默認從頂部順時針)progress_arc_color
、progress_backgroundColor
、progress_textColor
:顏色配置QPropertyAnimation* animation
:動畫對象指針
這些成員變量共同構成了進度顯示的完整狀態。
使用示例(簡要)
CCArcProgressWidget* widget = new CCArcProgressWidget(this);
widget->setValue(70);
widget->setMaxValue(100);
widget->setProgressArcColor(Qt::blue);
widget->setProgressBackgroundColor(Qt::lightGray);
widget->setProgressTextColor(Qt::black);
以上代碼將在界面中創建一個藍色的圓形進度條,表示當前進度為 70%。
一些實現的細節說明
? 下面的部分是屬性設置的接口,沒什么有趣的。
#include "CCArcProgressWidget.h"
#include <QPropertyAnimation>CCArcProgressWidget::CCArcProgressWidget(QWidget* parent): QWidget { parent } {setupAnimation();
}void CCArcProgressWidget::setupAnimation() {animation = new QPropertyAnimation(this, "displayValue");animation->setDuration(DURATION);animation->setEasingCurve(QEasingCurve::OutCubic);
}void CCArcProgressWidget::setValue(int val) {val = qBound(0, val, progress_max_value);if (val == progress_value) // avoid duplicate animationsreturn;progress_value = val;animation->stop();animation->setStartValue(progress_display_value);animation->setEndValue(progress_value);animation->start();
}
? 下面說下我們的繪制,這里是每一次觸發重繪的時候我們的設備實際上進行的繪制。
void CCArcProgressWidget::paintEvent(QPaintEvent* event [[maybe_unused]]) {QPainter painter(this);painter.setRenderHint(QPainter::Antialiasing, true);QRectF baseRect = rect();double side = qMin(baseRect.width(), baseRect.height());// 到這里,是為了獲取繪制成正方形而不是橢圓形(不然太難看了)QRectF squareRect((baseRect.width() - side) / 2.0,(baseRect.height() - side) / 2.0,side, side);int margin = ARC_WIDTH + 5;QRectF arcRect = squareRect.adjusted(margin, margin, -margin, -margin);double radius = qMin(arcRect.width(), arcRect.height()) / 2;QPointF center = arcRect.center();double angle = 360.0 * progress_display_value / progress_max_value;angle = qMax<double>(progress_minAngle, -angle);drawBackgroundArc(painter, arcRect);drawProgressArc(painter, arcRect, angle);drawText(painter, center, radius);
}
paintEvent
事件首先確定繪制區域arcRect
,再根據當前displayValue
計算對應的角度angle
。之后,依次調用:
drawBackgroundArc
:用圓弧繪制背景軌跡。drawProgressArc
:繪制當前進度的圓弧,同時在圓弧末端繪制小圓點,增強視覺效果。drawText
:居中繪制當前進度的百分比文本。
void CCArcProgressWidget::drawBackgroundArc(QPainter& painter, const QRectF& arcRect) {QPen pen(progress_backgroundColor, ARC_WIDTH);pen.setCapStyle(Qt::RoundCap);painter.setPen(pen);painter.drawArc(arcRect, progress_startAngle * 16, 360 * 16);
}void CCArcProgressWidget::drawProgressArc(QPainter& painter, const QRectF& arcRect, double angle) {if (angle == 0)return;QConicalGradient gradient(arcRect.center(), progress_startAngle);gradient.setColorAt(0, progress_arc_color.lighter(150));gradient.setColorAt(0.5, progress_arc_color);gradient.setColorAt(1, progress_arc_color.darker(150));QPen pen(QBrush(gradient), ARC_WIDTH);pen.setCapStyle(Qt::FlatCap);painter.setPen(pen);painter.drawArc(arcRect, progress_startAngle * 16, -angle * 16);double spanAngleRad = qDegreesToRadians(progress_startAngle - angle);double cx = arcRect.center().x();double cy = arcRect.center().y();double rx = arcRect.width() / 2;double ry = arcRect.height() / 2;double ex = cx + rx * qCos(spanAngleRad);double ey = cy - ry * qSin(spanAngleRad);QBrush brush(gradient);painter.setBrush(brush);painter.setPen(Qt::NoPen);painter.drawEllipse(QPointF(ex, ey), ARC_WIDTH / 2.0, ARC_WIDTH / 2.0);
}void CCArcProgressWidget::drawText(QPainter& painter, const QPointF& center, double radius) {painter.setFont(QFont("Arial", radius * 0.3, QFont::Bold));painter.setPen(progress_textColor);QString text = QString("%1%").arg(qRound(100.0 * progress_display_value / progress_max_value));QRectF textRect(center.x() - radius, center.y() - radius,radius * 2, radius * 2);painter.drawText(textRect, Qt::AlignCenter, text);
}