Qt 实现一个 火箭 圆形进度控件

Qt 实现一个 火箭 圆形进度控件

flyfish

文末有源码下载地址

关键词:Qt 6 · QWidget 自绘 · QPainter · 圆形进度环 · QTimer 动画 · 配色主题

一、先看效果

这是一个小 demo,但它把 Qt 自定义控件最常见的几个知识点都串起来了:

圆形进度环:一圈描边弧线随「扫描进度」从 0° 长到 360°;
滑块联动:拖动 QSlider 实时改变扫描角度,控件同步重绘;
扫描动画:点「开始扫描」后,QTimer 每隔 30ms 推进 12°,弧线循环扫动(约 3 秒一圈);
四套配色:一键切换,整个火箭的颜色跟着变。

火箭的脸、眼睛、翅膀、触角全是用 QPainter 的几何图元一笔笔画出来的,没有用任何图片素材。

请添加图片描述

二、项目结构

rocket_widget/
├── rocket_widget.pro   # qmake 工程文件
├── main.cpp            # 组装 UI:滑块 / 按钮 / 定时器
├── circleprogress.h    # CircleProgress 控件声明
└── circleprogress.cpp  # CircleProgress 的绘制逻辑

rocket_widget.pro 只有三行关键配置:

QT += widgets
TARGET = rocket_widget
TEMPLATE = app

三、原理

1. 自定义控件:继承 QWidget,重写 paintEvent

Qt 里所有"长得和自带控件不一样"的东西,本质都是子类化 QWidget + 重写 paintEvent

class CircleProgress : public QWidget {
    Q_OBJECT
public:
    explicit CircleProgress(QWidget *parent = nullptr);
    void setAngle(int a);                 // 0..360 扫描进度
    int angle() const { return m_angle; }
    void setColors(const QColor &main, const QColor &dark, const QColor &light);
protected:
    void paintEvent(QPaintEvent *event) override;
private:
    int m_angle;
    QColor m_main, m_dark, m_light;
};

设计:

状态与绘制分离m_angle 和三个颜色是"数据",paintEvent 只负责"把数据画出来"。外部永远通过 setAngle() / setColors() 改数据,而不是直接碰画笔。
改完就 update():两个 setter 结尾都调用了 update(),这是 Qt 约定的"请求重绘"入口,它会异步触发一次 paintEvent,而不是手动去调绘制函数。

2. Qt 的角度单位是 1/16 度

这是写圆形进度最容易被绊倒的地方。QPainter::drawArc 的角度参数不是普通度数,而是度的 1/16

int startAngle = 90 * 16;      // 90° × 16 = 1440,起点在 12 点钟方向
int spanAngle  = -m_angle * 16; // 负号表示逆时针扫动
p.drawArc(cx - r + 6, cy - r + 6, (r - 6) * 2, (r - 6) * 2, startAngle, spanAngle);

还要记住 Qt 的两条约定:

  • 角度 0° 在 3 点钟方向(不是 12 点);
  • 正值 顺时针,负值逆时针。

所以 90 * 16 把弧的起点从 3 点挪到了 12 点;spanAngle 取负,弧就从顶部开始往逆时针方向"长"出来。当 m_angle = 360 时正好画满一整圈。

3. 进度弧本身怎么画

p.setPen(QPen(m_main, 10, Qt::SolidLine, Qt::RoundCap)); // 主色、线宽10、圆头端点
p.drawArc(cx - r + 6, cy - r + 6, (r - 6) * 2, (r - 6) * 2, startAngle, spanAngle);

弧画在半径 r-6 = 58 的圆上,比底圆(r=64)小一圈,给描边留出余量,避免被控件边缘裁切;
Qt::RoundCap 让弧的两端是圆头,视觉更精致;
画笔 setPen 决定"线"的颜色和粗细,画填充图形前记得切回 setPen(Qt::NoPen)

4. 用几何图元拼出一张火箭脸

整个火箭没有任何贴图,全靠 drawEllipse / drawRoundedRect / drawPolygon 拼出来(坐标以圆心 (70,70) 为基准):

// 脸(主色大椭圆)
p.setBrush(m_main);
p.drawEllipse(cx - 32, cy - 28, 64, 56);

// 眼白
p.setBrush(Qt::white);
p.drawEllipse(cx - 12, cy - 14, 24, 36);

// 鼻子/嘴(圆角矩形)
p.drawRoundedRect(cx - 8, cy - 22, 16, 14, 7, 7);

// 左右翅膀(三角形多边形)
QPolygonF leftWing;
leftWing << QPointF(cx - 12, cy + 8) << QPointF(cx - 24, cy + 20) << QPointF(cx - 12, cy + 18);
p.drawPolygon(leftWing);
// 右翅膀对称……

// 触角上的小圆点
p.drawEllipse(cx - 6, cy + 20, 4, 8);
p.drawEllipse(cx + 2, cy + 20, 4, 8);

// 眼珠(暗色)
p.setBrush(m_dark);
p.drawEllipse(cx - 5, cy - 10, 10, 10);

一个小经验:setBrushdraw。Qt 的填充色是由当前画笔/画刷状态决定的,画下一个不同颜色的图形前,务必重新 setBrush,否则会沿用上一个颜色。

5. 三色主题:一个接口换一身皮肤

控件把颜色拆成三个语义角色,比"只给一个主色"灵活得多:

角色用途
m_main进度弧 + 脸 + 眼白之外的主体
m_dark描边感、眼珠等"重色"细节
m_light底盘背景的浅色

切换主题就是调一次 setColors

cp->setColors(QColor(0x22,0xCB,0x64), QColor(0x1B,0xA3,0x4D), QColor(0xE8,0xF5,0xE9)); // 常见绿
cp->setColors(QColor(0x20,0x9E,0xFF), QColor(0x16,0x7A,0xD9), QColor(0xE3,0xF2,0xFD)); // 科技蓝
cp->setColors(QColor(0xFF,0x4D,0x4F), QColor(0xD9,0x33,0x33), QColor(0xFD,0xEC,0xEC)); // 警示红

「随机色」还顺手演示了 QColor 的明度派生:

QColor main(rnd(), rnd(), rnd());
cp->setColors(main, main.darker(120), main.lighter(185)); // 主色自动推出暗色/浅色

darker() / lighter() 接收一个百分比因子,是做"同色系深浅"最省事的办法。

6. 扫描动画:QTimer + 滑块联动

动画没用任何高级框架,就是 QTimer 定时改角度:

QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&]() {
    static int ang = 0;
    ang = (ang + 12) % 360;     // 每次前进 12°
    cp->setAngle(ang);          // 改数据 → update()
    slider->setValue(ang);      // 顺手同步滑块,UI 不"各画各的"
});
// 点击按钮在 开始/停止 间切换
QObject::connect(scan, &QPushButton::clicked, [&]() {
    if (timer.isActive()) { timer.stop(); scan->setText("开始扫描"); }
    else { timer.start(30);  scan->setText("停止扫描"); }
});

30ms 一次、每次 +12°,数学上 360/12×30ms = 900ms?不对,应该是 360/12 = 30 步 × 30ms = 900ms 一圈。无论快慢,关键是:定时器只改"数据",绘制永远交给 paintEvent,职责清晰。

滑块和动画共用同一个 setAngle,所以手动拖和自动扫不会互相打架——这是"单一数据源"带来的好处。

四、完整代码

circleprogress.h

#ifndef CIRCLEPROGRESS_H
#define CIRCLEPROGRESS_H

#include <QWidget>
#include <QColor>

class CircleProgress : public QWidget {
    Q_OBJECT
public:
    explicit CircleProgress(QWidget *parent = nullptr);
    void setAngle(int a);
    int angle() const { return m_angle; }
    void setColors(const QColor &main, const QColor &dark, const QColor &light);
protected:
    void paintEvent(QPaintEvent *event) override;
private:
    int m_angle;
    QColor m_main, m_dark, m_light;
};

#endif // CIRCLEPROGRESS_H

circleprogress.cpp

#include "circleprogress.h"
#include <QPainter>
#include <QPolygonF>

CircleProgress::CircleProgress(QWidget *parent) : QWidget(parent), m_angle(0) {
    setFixedSize(140, 140);
    m_main  = QColor(0x22, 0xCB, 0x64);
    m_dark  = QColor(0x1B, 0xA3, 0x4D);
    m_light = QColor(0xE8, 0xF5, 0xE9);
}

void CircleProgress::setAngle(int a) {
    m_angle = qBound(0, a, 360);
    update();
}

void CircleProgress::setColors(const QColor &main, const QColor &dark, const QColor &light) {
    m_main = main; m_dark = dark; m_light = light;
    update();
}

void CircleProgress::paintEvent(QPaintEvent *) {
    QPainter p(this);
    p.setRenderHint(QPainter::Antialiasing);
    int cx = 70, cy = 70, r = 64;

    p.setPen(Qt::NoPen);
    p.setBrush(m_light);
    p.drawEllipse(cx - r, cy - r, r * 2, r * 2);

    int startAngle = 90 * 16;
    int spanAngle  = -m_angle * 16;
    p.setPen(QPen(m_main, 10, Qt::SolidLine, Qt::RoundCap));
    p.drawArc(cx - r + 6, cy - r + 6, (r - 6) * 2, (r - 6) * 2, startAngle, spanAngle);

    p.setPen(Qt::NoPen);
    p.setBrush(m_main);
    p.drawEllipse(cx - 32, cy - 28, 64, 56);

    p.setBrush(Qt::white);
    p.drawEllipse(cx - 12, cy - 14, 24, 36);
    p.drawRoundedRect(cx - 8, cy - 22, 16, 14, 7, 7);
    QPolygonF leftWing;
    leftWing << QPointF(cx - 12, cy + 8) << QPointF(cx - 24, cy + 20) << QPointF(cx - 12, cy + 18);
    p.drawPolygon(leftWing);
    QPolygonF rightWing;
    rightWing << QPointF(cx + 12, cy + 8) << QPointF(cx + 24, cy + 20) << QPointF(cx + 12, cy + 18);
    p.drawPolygon(rightWing);
    p.drawEllipse(cx - 6, cy + 20, 4, 8);
    p.drawEllipse(cx + 2, cy + 20, 4, 8);
    p.setBrush(m_dark);
    p.drawEllipse(cx - 5, cy - 10, 10, 10);
}

main.cpp

#include <QApplication>
#include <QPushButton>
#include <QSlider>
#include <QVBoxLayout>
#include <QLabel>
#include <QTimer>
#include <QColor>
#include <QRandomGenerator>
#include "circleprogress.h"

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);

    QWidget w;
    w.setWindowTitle(QString::fromUtf8("画火箭 (配色可变)"));

    CircleProgress *cp = new CircleProgress;

    QSlider *slider = new QSlider(Qt::Horizontal);
    slider->setRange(0, 360);
    slider->setValue(0);
    QObject::connect(slider, &QSlider::valueChanged, cp, &CircleProgress::setAngle);

    QPushButton *scan = new QPushButton(QString::fromUtf8("开始扫描"));
    QLabel *tip = new QLabel(QString::fromUtf8("拖动滑块调扫描进度,或点「开始扫描」播放循环动画;下方按钮切换配色主题"));
    tip->setWordWrap(true);

    QTimer timer;
    QObject::connect(&timer, &QTimer::timeout, [&]() {
        static int ang = 0;
        ang = (ang + 12) % 360;
        cp->setAngle(ang);
        slider->setValue(ang);
    });
    QObject::connect(scan, &QPushButton::clicked, [&]() {
        if (timer.isActive()) { timer.stop(); scan->setText(QString::fromUtf8("开始扫描")); }
        else { timer.start(30); scan->setText(QString::fromUtf8("停止扫描")); }
    });

    QPushButton *green = new QPushButton(QString::fromUtf8("常见绿"));
    QPushButton *blue  = new QPushButton(QString::fromUtf8("科技蓝"));
    QPushButton *red   = new QPushButton(QString::fromUtf8("警示红"));
    QPushButton *rand  = new QPushButton(QString::fromUtf8("随机色"));
    QObject::connect(green, &QPushButton::clicked, [&]() {
        cp->setColors(QColor(0x22,0xCB,0x64), QColor(0x1B,0xA3,0x4D), QColor(0xE8,0xF5,0xE9));
    });
    QObject::connect(blue, &QPushButton::clicked, [&]() {
        cp->setColors(QColor(0x20,0x9E,0xFF), QColor(0x16,0x7A,0xD9), QColor(0xE3,0xF2,0xFD));
    });
    QObject::connect(red, &QPushButton::clicked, [&]() {
        cp->setColors(QColor(0xFF,0x4D,0x4F), QColor(0xD9,0x33,0x33), QColor(0xFD,0xEC,0xEC));
    });
    QObject::connect(rand, &QPushButton::clicked, [&]() {
        auto rnd = []() { return QRandomGenerator::global()->bounded(256); };
        QColor main(rnd(), rnd(), rnd());
        cp->setColors(main, main.darker(120), main.lighter(185));
    });

    QVBoxLayout *lay = new QVBoxLayout(&w);
    lay->addWidget(cp, 0, Qt::AlignHCenter);
    lay->addWidget(new QLabel(QString::fromUtf8("扫描进度:")));
    lay->addWidget(slider);
    lay->addWidget(scan);
    lay->addWidget(tip);
    lay->addWidget(green);
    lay->addWidget(blue);
    lay->addWidget(red);
    lay->addWidget(rand);
    w.resize(300, 480);
    w.show();

    return a.exec();
}

五、清单

  1. 角度要乘 16drawArc / drawPie 的参数是 1/16 度,忘乘会得到几乎看不见的弧。
  2. update() 不是 repaint():前者合并多次请求、在下个事件循环重绘,性能更好;强行同步重绘请用 repaint()(一般没必要)。
  3. qBound 兜底setAngleqBound(0, a, 360) 把输入夹在合法区间,外部传脏数据也不会画出怪弧。
  4. setFixedSize 与绘制坐标耦合:控件固定 140×140,绘制时圆心写死 (70,70)。若想支持自适应大小,应改用 width()/2height()/2 动态计算,而不是硬编码。
  5. 抗锯齿别忘p.setRenderHint(QPainter::Antialiasing) 让弧和椭圆边缘平滑,否则在高分屏上会有明显锯齿。
  6. 画刷状态会"粘":连续画多个不同颜色的图元,记得每次 setBrush,否则颜色会串。

六、可以怎么扩展

绑定真实进度:现在 angle 是演示值,把它接到文件拷贝 / 网络请求的 bytesReceived / bytesTotal 上就是现成的进度环。
渐变描边:把 QPen 的颜色换成 QLinearGradient,进度弧立刻有质感。
呼吸光晕:在 paintEvent 里再叠一层带透明度的外圆,透明度用 QTimer 驱动,做"扫描中"的脉冲效果。
换成 SVG / 图标:若火箭要更精致,可用 QSvgRenderer 加载矢量图,再叠加手绘进度弧,比纯几何图元省事。
支持高 DPI 自适应:用 devicePixelRatio() 配合 QPainter 的变换,避免 4K 屏上控件发虚。

子类化 QWidget → 用成员变量存状态 → setter 里 update() → paintEvent 里用 QPainter 画图 → 用信号槽 / 定时器驱动状态变化。理解了这条主线,写仪表盘、波形图、加载动画都不在话下。

下载地址

内容概要:本文详细介绍了一种基于六维超混沌系统和DNA编码的彩色数字图像加密与解密方法,并系统分析了其抗噪声和抗裁剪性能,所有算法均通过Matlab代码实现。该方案充分利用六维超混沌系统对初值的高度敏感性和伪随机特性,结合DNA序列的生物特性和编码规则,设计了一套完整的图像加密流程,包括像素置乱、扩散变换以及DNA层级的加解密操作,从而显著提升了图像数据的安全性与保密性。文中还通过多种攻击测试(如高斯噪声、椒盐噪声和局部裁剪)验证了算法的鲁棒性,结果表明该加密机制在复杂攻击环境下仍能有效恢复原始图像,具备良好的实用价值与工程应用潜力。; 适合人群:具备Matlab编程基础,从事信息安全、图像处理或密码学相关研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①为数字图像在军事通信、医疗影像传输、金融信息安全等高敏感领域提供高强度加密保护方案;②研究混沌系统与生物编码相结合的新型图像加密机制的设计原理与实现路径;③评估加密算法在实际信道中面对噪声干扰与数据丢失时的恢复能力,优化其抗攻击性能。; 阅读建议:此资源以Matlab代码为核心载体,理论与实践紧密结合,建议读者在学习过程中动手运行并调试代码,深入理解混沌映射、DNA编码/解码规则及图像置乱扩散机制的实现细节,同时可通过修改参数或攻击类型进行扩展实验,全面提升对现代图像加密技术的认知与创新能力。
内容概要:本文提出了一种融合多尺度时序卷积网络(MS-TCN)与TiDE稠密编码器的深度学习模型,用于实现长周期电力负荷的直接多步预测。该模型通过多尺度卷积结构有效捕捉电力负荷序列在不同时间粒度下的局部模式与周期性特征,同时借助TiDE的编码-解码架构对全局时序依赖关系进行高效建模,从而提升中长期负荷预测的精度与鲁棒性。研究系统阐述了模型的整体架构设计、数据预处理流程、训练优化策略及实验验证过程,结果表明,该方法在多个真实电力负荷数据集上均显著优于传统的ARIMA、LSTM等基准模型以及单一结构的深度学习模型,具备良好的工程应用前景。此外,文中配套提供了完整的Python代码实现,便于读者复现与拓展。; 适合人群:具备一定深度学习基础和时间序列分析经验,从事电力系统规划、能源管理、智能电网等相关领域的科研人员、工程技术人员及研究生。; 使用场景及目标:①应用于电网企业开展中长期电力负荷预测,辅助制定发电计划、检修安排与调度策略;②为综合能源系统优化、电力市场竞价与需求响应等业务提供高精度的负荷数据支撑;③作为深度学习在能源预测领域的典型应用案例,促进人工智能技术在新型电力系统中的深度融合与落地实践。; 阅读建议:建议读者结合所提供的Python代码,动手复现模型并进行调试,深入理解多尺度卷积与TiDE结构的设计理念与协同机制,同时可在不同地区、不同季节的负荷数据集上开展迁移实验,以全面评估模型的泛化能力与适应性。
源码链接: https://pan.quark.cn/s/a4b39357ea24 ### C语言核心概念 #### 1. C语言简介 C语言是一种应用广泛的计算机编程语言,由Dennis Ritchie在1969年至1973年期间于AT&T的贝尔实验室设计。该语言因其高效性、灵活性及强大的功能,在系统软件、应用软件的开发领域中具有举足轻重的地位。 #### 2. 编程实践与PTA平台 编程实践是借助计算机语言进行问题逻辑分析、算法构建及编码实现的过程。PTA(Programming Teaching Assistant)是一个用于辅助编程教学实验的平台,学生能够通过该平台进行在线编程实践。 #### 3. 数据输入与输出操作 在C语言中,`scanf`和`printf`是常用的标准输入输出函数,分别用于从标准输入(通常为键盘)获取格式化的输入数据及向标准输出(通常为屏幕)显示格式化的输出数据。 ```c #include <stdio.h> int main() { int a, b; scanf("%d %d", &a, &b); // 从标准输入读取两个整数值 printf("%d", a + b); // 输出两个整数的和 return 0; } ``` #### 4. 数据类别与变量 C语言中的基本数据类别包含整型(int)、字符型(char)、浮点型(float, double)等。变量作为存储数据的载体,在使用前必须明确声明其数据类型。 ```c char a; int b; ``` #### 5. 字符数据输入输出操作 `getchar()`函数用于从标准输入获取下一个可用的字符,而`putchar()`函数则用于将字符输出到标准输出。 ``...
源码下载地址: https://pan.quark.cn/s/a4b39357ea24 《Eclipse Java 2022-03-R_win32-x86_64: 深入解析与应用》 Eclipse是一个功能卓越的开源集成开发环境(IDE),因其高效性、灵活性以及高度可扩展性而广受程序员的青睐。"eclipse-java-2022-03-R-win32-x86_64.zip" 是Eclipse为Java开发者精心打造的最新版本,专门为Windows x86_64架构进行了优化。该压缩文件内含运行Eclipse IDE所需的所有必要组件,使开发者在Windows平台上进行Java开发变得更为便捷。 1. **Eclipse IDE概述**:Eclipse自2001年起由IBM推出,最初作为一个Java集成开发环境,随后发展为一个通用的开发平台,支持多种编程语言和开发工具。其基于插件的架构允许用户依据需求进行功能的添加或移除,使其成为一款高度可定制的开发工具。 2. **Java开发支持**:Eclipse为Java开发者提供了全面的开发工具集,包括代码编辑器、调试器、构建工具、项目管理器等。其自动完成功能(Content Assist)和错误检测能显著提升开发效率,而强大的调试工具则能帮助开发者迅速定位和修正问题。 3. **R语言集成**:尽管Eclipse主要围绕Java展开,但通过安装特定的R语言插件(如Eclipse for R),开发者也能在同一个环境中编写和执行R代码,进行数据分析和统计建模。 4. **IDE概念**:集成开发环境(IDE)是一种融合了代码编辑、编译、调试和版本控制等多种功能的软件,它简化了开发流程,使开发者能够集中精力在代码编写上,而...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

二分掌柜的

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

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

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

打赏作者

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

抵扣说明:

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

余额充值