目标
- 基于QWidget绘制速度仪表盘, 仪表盘颜色为渐变色,
- 可指定当前显示的文字内容和文字颜色
- 可指定当前指针旋转的数值
效果图

控件完整代码
#pragma once#include <QWidget>
#include <QMap>class QPaintEvent;
class QPainter;///
/// @brief: 速度仪表控件, 类似网速的控件
///
class GaugeSpeedWidget : public QWidget
{Q_OBJECTpublic:explicit GaugeSpeedWidget(QWidget*parent = nullptr);virtual ~GaugeSpeedWidget();/// /// @brief: 设置指示器显示的数值/// @param: const qreal value /// @ret: void/// void setIndicatorValue(const qreal value);/// /// @brief: 设置指示器显示的单位/// @param: const QString & str /// @ret: void/// void setUnitText(const QString& str);/// /// @brief: 设置显示的数值保留几位小数,若为负数,则整数显示, 最大显示2位小数/// @param: const int count /// @ret: void/// void setIndicatorValueDecimal(const int count);private:void paintEvent(QPaintEvent* event);/// /// @brief: 绘制圆弧/// @param: QPainter * painter /// @ret: void/// void drawArc(QPainter* painter);/// /// @brief: 绘制圆弧上的刻度线/// @param: QPainter * painter /// @ret: void/// void drawArcDialLine(QPainter* painter);/// /// @brief: 绘制刻度数值/// @param: QPainter * painter /// @ret: void/// void drawArcDialLineNumber(QPainter* painter);/// /// @brief: 绘制指针/// @param: QPainter * painter /// @ret: void/// void drawIndicator(QPainter* painter);/// /// @brief: 绘制指示器的数值/// @param: QPainter * painter /// @ret: void/// void drawIndicatorValueText(QPainter* painter);private:struct PainterHelper;/// pie的半径qreal m_pieRadius{ 10.0 };/// 圆弧角度const qreal m_arcAngle{ 240.0 };/// 红色圆弧所占角度比例const qreal m_redArcAngleRate{ 0.2 };/// 蓝色圆弧所占角度比例const qreal m_blueArcAngleRate{ 0.4 };/// 绿色圆弧所占角度比例const qreal m_greenArcAngleRate{ 0.4 };/// 圆弧刻度线的配置, <key-索引,value-刻度线的颜色>QMap<int, QString> m_arcLineColorMap{};/// 当前指针的位置, 范围: 0~100, 若超过极值,指针固定在极值的位置qreal m_longIndicatorValue{0.0};/// 指针的填充颜色QString m_longIndicatorFillColor{"#B2DFF6"};/// 指针的边线的颜色QString m_longIndicatorBorderLineColor{ "#B2DFF6" };/// 显示数值的精度,小数点后的个数int m_indicatorValueDecimalPrecision{0};/// 显示的文本的颜色QString m_indicatorValueTextColor{"#18BED6"};/// 单位文本QString m_unitText{"KB/S"};/// 单位文本显示的颜色QString m_unitTextColor{"#18BED6"};
};
#include "GaugeSpeedWidget.h"
#include <QPainter>struct GaugeSpeedWidget::PainterHelper
{PainterHelper(QPainter& p) : m_painter(p){m_painter.save();}virtual ~PainterHelper(){m_painter.restore();}private:QPainter& m_painter;
};GaugeSpeedWidget::GaugeSpeedWidget(QWidget*parent): QWidget(parent)
{m_arcLineColorMap.insert(0, "#F22A78");m_arcLineColorMap.insert(1, "#F12A78");m_arcLineColorMap.insert(2, "#F12B78");m_arcLineColorMap.insert(3, "#0FB7ED");m_arcLineColorMap.insert(4, "#0FB7CC");m_arcLineColorMap.insert(5, "#10B7EC");m_arcLineColorMap.insert(6, "#2EC3F9");m_arcLineColorMap.insert(7, "#13B48D");m_arcLineColorMap.insert(8, "#14B38D");m_arcLineColorMap.insert(9, "#15B18D");m_arcLineColorMap.insert(10, "#17AF8E");setWindowFlags(Qt::FramelessWindowHint);setAttribute(Qt::WA_TranslucentBackground);
}GaugeSpeedWidget::~GaugeSpeedWidget()
{}///
/// @brief: setIndicatorValue
/// @param: const qreal value -
/// @ret: void
///
void GaugeSpeedWidget::setIndicatorValue(const qreal value)
{const qreal maxValue = 100.0;const qreal minValue = 0.0;m_longIndicatorValue = value;if (minValue > value){m_longIndicatorValue = minValue;}if (maxValue < value){m_longIndicatorValue = maxValue;}update();
}///
/// @brief: setUnitText
/// @param: const QString & str -
/// @ret: void
///
void GaugeSpeedWidget::setUnitText(const QString& str)
{m_unitText = str;update();
}///
/// @brief: setIndicatorValueDecimal
/// @param: const int count -
/// @ret: void
///
void GaugeSpeedWidget::setIndicatorValueDecimal(const int count)
{m_indicatorValueDecimalPrecision = count;const int maxValue = 2;const int minValue = 0;if (maxValue < m_indicatorValueDecimalPrecision){m_indicatorValueDecimalPrecision = maxValue;}if (minValue > m_indicatorValueDecimalPrecision){m_indicatorValueDecimalPrecision = minValue;}update();
}///
/// @brief: paintEvent
/// @param: QPaintEvent * event -
/// @ret: void
///
void GaugeSpeedWidget::paintEvent(QPaintEvent* event)
{if (false == isVisible()){return;}QPainter painter(this);painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing, true);int widgetWidth = this->width();int widgetHeight = this->height();int side = qMin(widgetWidth, widgetHeight);m_pieRadius = side * 0.9 / 2.0;//绘制准备工作,启用反锯齿,平移坐标轴中心,等比例缩放painter.translate(side / 2 + 4, side / 2 + 4);/// 绘制刻度线drawArcDialLine(&painter);// 绘制圆弧drawArc(&painter);/// 绘制刻度数值drawArcDialLineNumber(&painter);/// 绘制指针drawIndicator(&painter);/// 绘制指示器当前显示的数值drawIndicatorValueText(&painter);}///
/// @brief: 绘制圆弧
/// @param: QPainter * painter -
/// @ret: void
///
void GaugeSpeedWidget::drawArc(QPainter* painter)
{PainterHelper ph(*painter);/// 保持边距const qreal alignOffset = 0;/// 圆弧所在矩形QRectF outerRect{ -m_pieRadius + alignOffset, -m_pieRadius + alignOffset, m_pieRadius * 2, m_pieRadius * 2 };// 定义圆弧的参数//QRectF outerRect(50, 50, 200, 200); // 外圆弧的外接矩形//QRectF innerRect(60, 60, 180, 180); // 内圆弧的外接矩形(通过缩小矩形实现宽度)QRectF innerRect(outerRect.x() + 10, outerRect.y() + 10, outerRect.width() - 20, outerRect.height() - 20);int startAngle = -30 * 16; // 起始角度,单位为1/16度int totalSpan = 240 * 16; // 总跨度角度,单位为1/16度// 创建渐变色QConicalGradient gradient(outerRect.center(), startAngle / 16); // 渐变色中心点与起始角度对齐gradient.setColorAt(0.0, QColor("#F22A78")); gradient.setColorAt(48 / 240.0, QColor("#0FB7EE")); gradient.setColorAt(144 / 240.0, QColor("#2AEBA2")); gradient.setColorAt(1, QColor("#00FF00"));// 创建外圆弧路径QPainterPath outerPath;outerPath.arcMoveTo(outerRect, startAngle / 16); // 移动到起始点outerPath.arcTo(outerRect, startAngle / 16, totalSpan / 16); // 绘制外圆弧// 创建内圆弧路径QPainterPath innerPath;innerPath.arcMoveTo(innerRect, (startAngle + totalSpan) / 16); // 移动到终点innerPath.arcTo(innerRect, (startAngle + totalSpan) / 16, -totalSpan / 16); // 绘制内圆弧(反向)// 将两条路径连接起来QPainterPath path;path.addPath(outerPath);path.connectPath(innerPath);// 使用渐变色填充路径painter->fillPath(path, gradient);}///
/// @brief: drawArcDialLine
/// @param: QPainter * painter -
/// @ret: void
///
void GaugeSpeedWidget::drawArcDialLine(QPainter* painter)
{PainterHelper ph(*painter);painter->setBrush(Qt::NoBrush);const qreal lineLen = 13;qreal pieRadius = m_pieRadius;/// colorStr-划线的颜色/// rotateAngle - 较零位的旋转角度auto drawArcLine = [&painter, &lineLen, &pieRadius](const QString& colorStr, const qreal rotateAngle){painter->setPen(QPen(QColor(colorStr), 2));painter->rotate(rotateAngle);painter->drawLine(pieRadius - lineLen, 0, pieRadius - 1, 0);};/// 总共需要绘制11根刻度线,const qint32 lineCount = 11;/// 绘制每根刻度线需要旋转的角度const qreal rotateAngle = m_arcAngle / (lineCount - 1);/// 多旋转, 是为了统一绘制刻度线. 30是起始角度painter->rotate(rotateAngle + 30);for (int index = 0; index < lineCount; ++index){/// 得到刻度线的颜色const QString colorStr = m_arcLineColorMap.value(index);/// 逆时针绘制刻度线,所以旋转角度为负数drawArcLine(colorStr, -rotateAngle);}
}///
/// @brief: drawArcDialLineNumber
/// @param: QPainter * painter -
/// @ret: void
///
void GaugeSpeedWidget::drawArcDialLineNumber(QPainter* painter)
{PainterHelper ph(*painter);qreal textRadius = m_pieRadius * 0.75;QFontMetrics fm(painter->font());/// 总共需要绘制11根刻度线,const qint32 lineCount = 11;/// 绘制每根刻度线需要旋转的角度const qreal rotateAngle = m_arcAngle / (lineCount - 1);/// 绘制数值的方向时顺时针, 从左向右绘制,qreal startAngle = 60 + 90;//for (int index = 100; index >= 0; index -= 10)for (int index = 0; index < lineCount; ++ index){QString valueStr = QString::number(index * 10);int tmpAngle = startAngle + index * rotateAngle;qreal angleArc = (tmpAngle % 360) * 3.14159265 / 180.0;qreal textXX = (textRadius) * cos(angleArc);qreal textYY = (textRadius) * sin(angleArc);int textW = (int)fm.width(valueStr);int textH = (int)fm.height();textXX -= textW / 2.0;textYY -= textH / 2.0;const QString textColor = m_arcLineColorMap[lineCount - 1 - index];painter->setPen(QColor(textColor));painter->drawText(textXX, textYY, textW, textH, Qt::AlignCenter, valueStr);}}///
/// @brief: 绘制长指针
/// @param: QPainter * painter -
/// @ret: void
///
void GaugeSpeedWidget::drawIndicator(QPainter* painter)
{PainterHelper ph(*painter);/// 长指针是一个四边形组成/// 指针偏转方向: 顺时针,const qreal startAngle = 60 + 90;/// 表盘的范围时0~100,角度范围 240度。 计算当前指针的偏转角度const qreal tmpIndicatorAngle = m_longIndicatorValue * (240.0 / 100);/// 指针的偏转角度=起始角度+当前角度const qreal indicatorAngle = startAngle + tmpIndicatorAngle;/// 旋转指定角度painter->rotate(indicatorAngle);/// 绘制固定的四边形, 长指针较长的一端指向X的正半轴QPointF longIndicatorPointArr[4] = {QPointF(-8, 0),QPointF(0, -4),QPointF(m_pieRadius * 1.0, 0),QPointF(0, 4)};painter->setPen(QColor(m_longIndicatorBorderLineColor));painter->setBrush(QColor(m_longIndicatorFillColor));painter->drawPolygon(longIndicatorPointArr, 4);
}///
/// @brief: drawIndicatorValueText
/// @param: QPainter * painter -
/// @ret: void
///
void GaugeSpeedWidget::drawIndicatorValueText(QPainter* painter)
{PainterHelper ph(*painter);const QString valueStr = QString("%1").arg(m_longIndicatorValue, 0, 'f', m_indicatorValueDecimalPrecision);painter->setBrush(Qt::NoBrush);painter->setPen(QColor(m_indicatorValueTextColor));QRectF valueRect{};{QFont fontTmp = painter->font();fontTmp.setPointSize(22);painter->setFont(fontTmp);QFontMetrics fm(fontTmp);/// 计算文字显示的矩形方框qreal textW = (m_pieRadius * 0.7 * 0.8) * 2;qreal textH = fm.height();qreal textYY = m_pieRadius * 0.7 * 0.5;painter->drawText(-textW / 2, textYY, textW, textH, Qt::AlignCenter, valueStr);valueRect = QRectF{ -textW / 2, textYY, textW, textH };}/// 绘制显示的单位QFont fontTmp = painter->font();fontTmp.setPointSize(14);painter->setFont(fontTmp);QFontMetrics fm(fontTmp);/// 单位显示的宽和高为数值显示的宽高相同qreal textWW = valueRect.width();qreal textHH = fm.height();qreal textXX = valueRect.x();qreal textYY = valueRect.y() + valueRect.height() + 3;painter->setPen(QColor(m_unitTextColor));painter->drawText(textXX, textYY, textWW, textHH, Qt::AlignCenter, m_unitText);
}