前言
积累一些常用需要绘制的控件。
效果图



支持3档变色,源代码在资源中。
QGraphics框架核心概念
| 组件/概念 | 功能描述 | 关键特性 |
|---|---|---|
| QGraphics框架整体 | Graphics View Framework - 管理2D图形对象的完整框架 | 专为大量自定义、可交互的2D图形设计 |
| **QGraphicsScene(场景)** | 不可见的画布,管理所有图形项 | • 管理图形项集合 • 碰撞检测 • 事件传播 |
| **QGraphicsView(视图)** | 显示场景内容的可视化组件 | • 多个视图可观察同一场景 • 支持缩放、旋转 • 滚动和变换 |
| **QGraphicsItem(图形项)** | 场景中的基本图形单元 | • 基础图形(圆形、矩形等) • 可交互的自定义项 • 支持鼠标、键盘事件 |
详细讲解
本篇按照5个步骤实现效果图中的仪表盘:
- 绘制背景(外圆和内圆)
- 绘制进度弧
- 绘制刻度线和刻度值
- 绘制中心文本(分数和单位)
- 绘制底部文字
绘制背景(外圆和内圆)
绘制要点:
-
先绘制一个大圆,这个大圆的边界加粗;
-
绘制一个小圆,不设边界 ,小圆会覆盖与大圆交集的部分;
-
大圆与小圆的中心点要一致。
//绘制背景(外圆和内圆)
void ScoreMeter::drawBackground(QPainter painter)
{
//创建径向渐变用于外圆背景
//圆圈中渐变色
QRadialGradient gradient(0, 0, m_radius+10);
gradient.setColorAt(0, QColor("#5CF9FE"));//中心颜色(较亮)
gradient.setColorAt(1, QColor("#14141E"));//边缘颜色(较暗)
painter->setBrush(gradient);//设置画刷为渐变,设置画笔为深灰色边框
//边界圈样式
painter->setPen(QPen(QColor("#32323C"), 6));//6像素宽的边框
//绘制外圆
painter->drawEllipse(-m_radius, -m_radius, 2m_radius, 2*m_radius);//创建内圆的径向渐变---会覆盖与外圆交集的区域 int inner=m_radius-30;//内外圆之间的距离 QRadialGradient innerGradient(0, 0, inner); innerGradient.setColorAt(0, QColor("#282832"));//中心颜色(较暗) innerGradient.setColorAt(1, QColor("#1E1E28"));//边缘颜色(较暗) painter->setBrush(innerGradient); //不设置边界 painter->setPen(Qt::NoPen); //绘制内圆 painter->drawEllipse(-inner, -inner, 2*inner, 2*inner);}
绘制进度弧
绘制要点:
-
设置最高分数点,以此来做180°的刻画;
-
设置起始点在左边:-180 * 16;
-
设置3个阶段显示的颜色;
-
设置进度宽度;
-
设置透明色宽度;(可选的效果)
//绘制进度弧
void ScoreMeter::drawProgressArc(QPainter *painter)
{
int radius=m_radius-10;//与外圈的距离
//进度条比例:当前分数除以100
double progress = static_cast<double>(m_score) / 100.0;
//计算圆弧的跨度角度(以1/16度为单位)
//180度对应半圆,乘以16转换为Qt的角度单位
int spanAngle = -static_cast<int>(progress * 180 * 16);
int startAngle = -180 * 16; //左侧起始位置
//根据分数值选择颜色
QColor mainColor, glowColor;
glowColor = QColor("#64B4FF96"); //半透明色if (m_score <= 10) { //10分以内显示红色 mainColor = QColor("#FF4141"); //红色 } else if (m_score < 60) { //10-60分显示绿色 mainColor = QColor("#41FF41"); //绿色 } else { //60分以上显示蓝色 mainColor = QColor("#4196FF"); //蓝色 } //创建主进度弧的画笔(蓝色,10像素宽) QPen arcPen(mainColor, 10); //arcPen.setCapStyle(Qt::RoundCap); //设置线帽为圆角 painter->setPen(arcPen); //绘制主进度弧: //-180 * 16表示从底部开始(Qt的角度0度在3点钟方向,-180度在6点钟方向) painter->drawArc(-radius, -radius, 2*radius, 2*radius, startAngle,spanAngle); //以下是可选的效果,可以注释掉 QPen glowPen(glowColor, 6);//#64B4FF96的96是透明度(150/255 ≈ 0.59) //arcPen.setCapStyle(Qt::RoundCap); //设置线帽为圆角 painter->setPen(glowPen); painter->drawArc(-radius, -radius, 2*radius, 2*radius, startAngle, spanAngle);}
绘制刻度线和刻度值
绘制要点:
-
设置刻度从0~10;
-
设置没间隔一个刻度显示数字;
-
数字作为文本进行绘制;
//绘制刻度线和刻度值
void ScoreMeter::drawScaleMarks(QPainter *painter)
{
//设置刻度线的画笔(浅灰色,半透明,2像素宽)
painter->setPen(QPen(QColor("#C8C8C864"), 2));
//绘制11个刻度(0%到100%,每10%一个刻度)
for (int i = 0; i <= 10; ++i) {
double angle = -180 + (i * 18);
//将角度转换为弧度
double rad = angle * M_PI / 180.0;
//计算刻度线的起点和终点坐标(在85-95半径范围内)
double x1 = 85 * cos(rad);
double y1 = 85 * sin(rad);
double x2 = 95 * cos(rad);
double y2 = 95 * sin(rad);
//绘制刻度线
painter->drawLine(x1, y1, x2, y2);
//每2个刻度(即20%)绘制一个数字标签
if (i % 2 == 0) {
QString text = QString::number(i * 10);
QFont font("Arial", 8);//设置刻度数字字体
painter->setFont(font);
painter->setPen(QPen(QColor("#969696")));//刻度数字颜色(灰色)double textX = 75 * cos(rad);//文本X坐标 double textY = 75 * sin(rad);//文本Y坐标 QFontMetrics metrics(font); QRect textRect = metrics.boundingRect(text);//获取文本的矩形区域 //绘制文本,使其居中于计算的位置 //textX - textRect.width()/2: 水平居中 //textY + textRect.height()/4: 垂直位置调整(不是严格居中) painter->drawText(textX - textRect.width()/2, textY + textRect.height()/4, text); } }}
绘制中心文本(分数和单位)
绘制要点:
-
设置字体格式;
-
分数设置为水平居中;
-
单位设置与分数的底部平齐;
//绘制中心文本
void ScoreMeter::drawCenterText(QPainter *painter)
{
painter->save();//保存 painter 的当前状态(变换、画笔、画刷等)
painter->setRenderHint(QPainter::TextAntialiasing, true);//启用文本抗锯齿//根据图片调整字体大小 QFont scoreFont("Arial", 36, QFont::Bold); //数值大小,QFont::Bold 粗体 QFont unitFont("Arial", 18); //单位大小 QFontMetrics scoreMetrics(scoreFont); QFontMetrics unitMetrics(unitFont); QString scoreText = QString::number(m_score); //获取文本的边界矩形 QRect scoreRect = scoreMetrics.boundingRect(scoreText); QRect unitRect = unitMetrics.boundingRect(m_unittext); //计算组合文本的总宽度 int spacing = 2; //稍微增加间距 int totalWidth = scoreRect.width() + spacing + unitRect.width(); int scoreBaselineY = (scoreMetrics.ascent() - scoreMetrics.descent()) / 2;//垂直居中 int unitBaselineY = scoreBaselineY;//与分数的底部平齐 //水平居中位置 int startX = -totalWidth / 2;//数值的X坐标 //绘制分数数字 painter->setFont(scoreFont); painter->setPen(QPen(Qt::white)); painter->drawText(startX, scoreBaselineY, scoreText); //绘制单位 painter->setFont(unitFont); painter->setPen(QPen(QColor("#AAAABE")));//调整颜色更接近图片 int unitX = startX + scoreRect.width() + spacing;//单位的x坐标,在分数的后面 painter->drawText(unitX, unitBaselineY, m_unittext); painter->restore();//恢复 painter 的之前状态}
绘制底部文字
绘制要点:
-
设置字体格式;
-
从底部开始把位置上移。
//绘制底部文字
void ScoreMeter::drawBottomText(QPainter *painter)
{
QFont font("Arial", 10);
painter->setFont(font);
painter->setPen(QPen(QColor("#AAAABE")));QFontMetrics metrics(font); QRect textRect = metrics.boundingRect(m_Bottomtext); //在底部绘制文字,Y轴坐标:-20(外环距离)-10(字体大小)-10(字体与内环边界的距离) painter->drawText(-textRect.width()/2, m_radius-20-10-10, m_Bottomtext);}
控件源码
//scoremeter.h
#ifndef SCOREMETER_H
#define SCOREMETER_H
#include <QGraphicsItem>
#include <QPainter>
#include <QDebug>
#include <iostream>
class ScoreMeter : public QGraphicsItem
{
public:
explicit ScoreMeter(int score, QString unit, QString Bottom,QGraphicsItem *parent = nullptr);
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
void setScore(int score);//设置分数的方法
private:
int m_score;
QString m_unittext;
QString m_Bottomtext;
int m_radius;//外圆半径
void drawBackground(QPainter *painter);
void drawProgressArc(QPainter *painter);
void drawScaleMarks(QPainter *painter);
void drawCenterText(QPainter *painter);
void drawBottomText(QPainter *painter);
};
#endif // SCOREMETER_H
//scoremeter.cpp
#include "scoremeter.h"
#include <QLinearGradient>
#include <QRadialGradient>
#include <QFont>
#include <QFontMetrics>
#include <cmath>
ScoreMeter::ScoreMeter(int score, QString unit, QString Bottom, QGraphicsItem *parent)
: QGraphicsItem(parent), m_score(score), m_unittext(unit), m_Bottomtext(Bottom)
{
m_radius=100;
setFlag(QGraphicsItem::ItemIsSelectable, false);//禁止选择该图形项
setFlag(QGraphicsItem::ItemIsMovable, false);//禁止移动该图形项
}
//返回图形项的边界矩形
//这个矩形定义了图形项的绘制区域和碰撞检测区域
QRectF ScoreMeter::boundingRect() const
{
//返回一个以(0,0)为中心,宽高各240的矩形
//实际绘制区域是200x200的圆,额外留出20像素的边距
return QRectF(-120, -120, 240, 240);
}
//绘制函数 - 主要的绘制逻辑在这里实现
//参数: painter - 用于绘制的画笔, option - 样式选项, widget - 关联的部件
void ScoreMeter::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option)//声明不使用
Q_UNUSED(widget)//声明不使用
//启用抗锯齿,使图形边缘更平滑
painter->setRenderHint(QPainter::Antialiasing, true);
//按照从底层到顶层的顺序绘制各个组件:
drawBackground(painter); //1.绘制背景(外圆和内圆)
drawProgressArc(painter); //2.绘制进度弧(蓝色半圆)
drawScaleMarks(painter); //3.绘制刻度线和刻度值
drawCenterText(painter); //4.绘制中心文本(分数和单位)
drawBottomText(painter); //5.绘制底部文字
}
//绘制背景(外圆和内圆)
void ScoreMeter::drawBackground(QPainter *painter)
{
//创建径向渐变用于外圆背景
//圆圈中渐变色
QRadialGradient gradient(0, 0, m_radius+10);
gradient.setColorAt(0, QColor("#5CF9FE"));//中心颜色(较亮)
gradient.setColorAt(1, QColor("#14141E"));//边缘颜色(较暗)
painter->setBrush(gradient);//设置画刷为渐变,设置画笔为深灰色边框
//边界圈样式
painter->setPen(QPen(QColor("#32323C"), 6));//6像素宽的边框
//绘制外圆
painter->drawEllipse(-m_radius, -m_radius, 2*m_radius, 2*m_radius);
//创建内圆的径向渐变---会覆盖与外圆交集的区域
int inner=m_radius-30;//内外圆之间的距离
QRadialGradient innerGradient(0, 0, inner);
innerGradient.setColorAt(0, QColor("#282832"));//中心颜色(较暗)
innerGradient.setColorAt(1, QColor("#1E1E28"));//边缘颜色(较暗)
painter->setBrush(innerGradient);
//不设置边界
painter->setPen(Qt::NoPen);
//绘制内圆
painter->drawEllipse(-inner, -inner, 2*inner, 2*inner);
}
//绘制进度弧(蓝色半圆进度条)
void ScoreMeter::drawProgressArc(QPainter *painter)
{
int radius=m_radius-10;//与外圈的距离
//进度条比例:当前分数除以100
double progress = static_cast<double>(m_score) / 100.0;
//计算圆弧的跨度角度(以1/16度为单位)
//180度对应半圆,乘以16转换为Qt的角度单位
int spanAngle = -static_cast<int>(progress * 180 * 16);
int startAngle = -180 * 16; //左侧起始位置
//根据分数值选择颜色
QColor mainColor, glowColor;
glowColor = QColor("#64B4FF96"); //半透明色
if (m_score <= 10) {
//10分以内显示红色
mainColor = QColor("#FF4141"); //红色
} else if (m_score < 60) {
//10-60分显示绿色
mainColor = QColor("#41FF41"); //绿色
} else {
//60分以上显示蓝色
mainColor = QColor("#4196FF"); //蓝色
}
//创建主进度弧的画笔(蓝色,10像素宽)
QPen arcPen(mainColor, 10);
//arcPen.setCapStyle(Qt::RoundCap); //设置线帽为圆角
painter->setPen(arcPen);
//绘制主进度弧:
//-180 * 16表示从底部开始(Qt的角度0度在3点钟方向,-180度在6点钟方向)
painter->drawArc(-radius, -radius, 2*radius, 2*radius, startAngle,spanAngle);
QPen glowPen(glowColor, 6);//#64B4FF96的96是透明度(150/255 ≈ 0.59)
//arcPen.setCapStyle(Qt::RoundCap); //设置线帽为圆角
painter->setPen(glowPen);
painter->drawArc(-radius, -radius, 2*radius, 2*radius, startAngle, spanAngle);
}
//绘制刻度线和刻度值
void ScoreMeter::drawScaleMarks(QPainter *painter)
{
//设置刻度线的画笔(浅灰色,半透明,2像素宽)
painter->setPen(QPen(QColor("#C8C8C864"), 2));
//绘制11个刻度(0%到100%,每10%一个刻度)
for (int i = 0; i <= 10; ++i) {
double angle = -180 + (i * 18);
//将角度转换为弧度
double rad = angle * M_PI / 180.0;
//计算刻度线的起点和终点坐标(在85-95半径范围内)
double x1 = 85 * cos(rad);
double y1 = 85 * sin(rad);
double x2 = 95 * cos(rad);
double y2 = 95 * sin(rad);
//绘制刻度线
painter->drawLine(x1, y1, x2, y2);
//每2个刻度(即20%)绘制一个数字标签
if (i % 2 == 0) {
QString text = QString::number(i * 10);
QFont font("Arial", 8);//设置刻度数字字体
painter->setFont(font);
painter->setPen(QPen(QColor("#969696")));//刻度数字颜色(灰色)
double textX = 75 * cos(rad);//文本X坐标
double textY = 75 * sin(rad);//文本Y坐标
QFontMetrics metrics(font);
QRect textRect = metrics.boundingRect(text);//获取文本的矩形区域
//绘制文本,使其居中于计算的位置
//textX - textRect.width()/2: 水平居中
//textY + textRect.height()/4: 垂直位置调整(不是严格居中)
painter->drawText(textX - textRect.width()/2, textY + textRect.height()/4, text);
}
}
}
//绘制中心文本
void ScoreMeter::drawCenterText(QPainter *painter)
{
painter->save();//保存 painter 的当前状态(变换、画笔、画刷等)
painter->setRenderHint(QPainter::TextAntialiasing, true);//启用文本抗锯齿
//根据图片调整字体大小
QFont scoreFont("Arial", 36, QFont::Bold); //数值大小,QFont::Bold 粗体
QFont unitFont("Arial", 18); //单位大小
QFontMetrics scoreMetrics(scoreFont);
QFontMetrics unitMetrics(unitFont);
QString scoreText = QString::number(m_score);
//获取文本的边界矩形
QRect scoreRect = scoreMetrics.boundingRect(scoreText);
QRect unitRect = unitMetrics.boundingRect(m_unittext);
//计算组合文本的总宽度
int spacing = 2; //稍微增加间距
int totalWidth = scoreRect.width() + spacing + unitRect.width();
int scoreBaselineY = (scoreMetrics.ascent() - scoreMetrics.descent()) / 2;//垂直居中
int unitBaselineY = scoreBaselineY;//与分数的底部平齐
//水平居中位置
int startX = -totalWidth / 2;//数值的X坐标
//绘制分数数字
painter->setFont(scoreFont);
painter->setPen(QPen(Qt::white));
painter->drawText(startX, scoreBaselineY, scoreText);
//绘制单位
painter->setFont(unitFont);
painter->setPen(QPen(QColor("#AAAABE")));//调整颜色更接近图片
int unitX = startX + scoreRect.width() + spacing;//单位的x坐标,在分数的后面
painter->drawText(unitX, unitBaselineY, m_unittext);
painter->restore();//恢复 painter 的之前状态
}
//绘制底部文字
void ScoreMeter::drawBottomText(QPainter *painter)
{
QFont font("Arial", 10);
painter->setFont(font);
painter->setPen(QPen(QColor("#AAAABE")));
QFontMetrics metrics(font);
QRect textRect = metrics.boundingRect(m_Bottomtext);
//在底部绘制文字,Y轴坐标:-20(外环距离)-10(字体大小)-10(字体与内环边界的距离)
painter->drawText(-textRect.width()/2, m_radius-20-10-10, m_Bottomtext);
}
//添加setScore方法实现
void ScoreMeter::setScore(int score)
{
m_score = score;
update(); //触发重绘
}
效果图中的MainWindow的代码
如下图,UI上添加3个控件,并且按钮右键菜单"转到槽..."

以下是MainWindow的代码:
//mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QGraphicsView>
#include <QGraphicsScene>
#include "scoremeter.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_pushButton_clicked();
private:
Ui::MainWindow *ui;
QGraphicsScene *scene;
ScoreMeter *scoreMeter;
void setupScoreMeter();
};
#endif // MAINWINDOW_H
//mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, scene(nullptr)
, scoreMeter(nullptr)
{
ui->setupUi(this);
setWindowTitle("QGraphicsView圆形表盘");
//创建图形场景
scene = new QGraphicsScene(this);
//设置场景到graphicsView
ui->graphicsView->setScene(scene);
//设置视图属性
ui->graphicsView->setRenderHint(QPainter::Antialiasing);//抗锯齿
ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);//禁用水平滚动条
ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);//禁用垂直滚动条
//去掉边框,背景设为透明
ui->graphicsView->setStyleSheet("QGraphicsView { border: none;background: transparent; }");
//初始化ScoreMeter
setupScoreMeter();
}
MainWindow::~MainWindow()
{
delete ui;
delete scene;
}
void MainWindow::setupScoreMeter()
{
//如果已存在ScoreMeter,先移除
if (scoreMeter) {
scene->removeItem(scoreMeter);
delete scoreMeter;
}
//获取初始分数(默认为0)
int initialScore = 0;
if (!ui->score->text().isEmpty()) {
bool ok;
int score = ui->score->text().toInt(&ok);
if (ok && score >= 0 && score <= 100) {
initialScore = score;
}
}
//创建新的ScoreMeter
scoreMeter = new ScoreMeter(initialScore, "分", "性能评分");
scene->addItem(scoreMeter);
//居中显示
ui->graphicsView->centerOn(scoreMeter);
//可选:调整视图大小
ui->graphicsView->setFixedSize(210, 210);
}
void MainWindow::on_pushButton_clicked()
{
//获取文本框中的分数
if (ui->score->text().isEmpty()) {
QMessageBox::warning(this, "输入错误", "请输入分数(0-100)");
return;
}
bool ok;
int newScore = ui->score->text().toInt(&ok);
if (!ok || newScore < 0 || newScore > 100) {
QMessageBox::warning(this, "输入错误", "请输入0-100之间的有效分数");
return;
}
//更新ScoreMeter的分数
if (scoreMeter) {
scoreMeter->setScore(newScore);
}
}
.pro文件
QT += core gui widgets