QT绘制简易表盘
1、简介
最近学习了一下QT,熟悉了一段时间后发现它的功能还是挺强大的,同时也比较方便用户上手去使用。现在就基于最近学习的内容,实现一个简易的带指针旋转功能的表盘。文中表盘的实现是基于QT的QPainter类中的绘图方法,同时外加QT的定时器设计完成的。效果上肯定没有贴图片那么美观,不过两者的设计思想是基本一样的,这里的设计方法可以提供给你一个不错的参考。
2、设计思路
在讲解设计思路前还是先来看下绘制的表盘所实现的效果吧!后面对实现效果进行一步步拆解来讲解,这样或许会更加直观,更便于理解。
我们先不去考虑指针旋转这一动态过程实现,可以将表盘分解析为3个组成部分,表盘的外形轮廓、指针和显示的当前速度的数值。外形轮廓由一个圆弧和一些指示刻度组成,它的绘制肯定要使用QT中的画圆弧的函数、画线函数还有显示文本函数。指针是一个不规则的多边形,它的绘制会用到QT中的绘制多边形的函数。显示当前速度值比较简单些,直接使用显示文本函数绘制。
有了静态部分的基础,现在开始考虑指针的动态旋转过程和旋转过程中的渐变效果是如何实现的。指针旋转的角度应该和当前的转速相互对应,当前转速改变时,会根据新的转速计算出当前指针位于什么角度的位置,然后可以调用QT的旋转角度函数让多边形指针旋转到这个位置。旋转的渐变效果其实是通过绘制扇形实现的,要绘制扇形的角度和指针旋转的角度是一样,由于绘制的扇形的内部的着色采用了颜色的线性内插,所以不同的角度显示的颜色程度不同,因此给人以渐变的效果。
转速的周期改变是在定时器中完成的,构造函数中初始化一个周期定时器,当定时器时间超时,根据当前转速的状态(上行还是下行),确定让转速自增1还是自减1。转速改变时,调用函数让界面进行重新绘制。
3、核心函数
所用到的绘制函数都是QPainter类中的方法, QPainter可以理解成是个画家,这个画家有很多种绘制图形的方法,它可以在所以继承QPaintDevice的类上进行绘制。
绘制多边形:
drawPolygon
QT中drawPolygon有很多重载版本,要传入的参数一般就是要指定绘制多边形的所有顶点位置。
绘制圆弧:
drawArc
同理,QT中也有很多drawArc的重载版本。它传入的参数比较特殊,QT中的绘制圆弧是在一个正方形中操作的,要传入的是正方形区域的大小,绘制圆弧的圆心默认是在传入正方形区域中心。除此之外还需要指定要绘制圆弧的起始角度和跨越的角度,它传入是值是实际角度*16。 当传入的角度值为正,表示逆时针绘制圆弧,角度值为负,表示顺时钟绘制圆弧。
绘制扇形:
drawPie
绘制扇形和绘制圆弧的参数类似,可参考绘制圆弧。
QPainter坐标转换:
translate
如执行painter.translate(100, 100),后续绘制的参考坐标变成了相对100, 100位置而言的。执行painter.drawLine(0, 0, 0, 20),实际绘制直线的两端点是(100, 100)和(100, 120)
旋转:
ratate
坐标转换和旋转过程可参考下图:
保存和恢复QPainter状态:
save
restore
save保存当前QPainter状态(即当前对QPainter的设置)到堆栈,restore恢复之前保存的状态。这两个方法在QPainter绘图中很有用,详细可参考后面代码。
4、代码实现
先从定时器函数函数来看,当构造函数中设置的定时器超时,会产生定时器事件,timerEvent被自动调用
void Widget::timerEvent(QTimerEvent *e) { int timerId = e->timerId(); if(this->time_id == timerId) { if(this->status == 0) { this->speed += 1; if(this->speed >= 180) this->status = 1; }else { this->speed -= 1; if(this->speed <= 0) this->status = 0; } this->update(); } }
判断当前转速是处于上行还是下行状态,根据转速所处的状态修改转速。然后调用this->update(), 该函数会产生Widget类中的重绘事件,paintEvent函数被执行。
painteEvent函数内容如下:
void Widget::paintEvent(QPaintEvent *event) { qreal angle; angle = (qreal)270 / (180-1); QPainter painter(this); painter.translate(width()/2, height()/2); drawFrame(&painter, angle); //① drawPointer(&painter, angle); //② drawSpeed(&painter); //③ }
① 绘制仪表盘圆弧形状的外框
② 根据转速大小,计算当前指针所在的角度,然后绘制指针和产生渐变效果的扇形
③ 显示当前转速值
drawFrame函数
void Widget::drawFrame(QPainter *painter, qreal angle) { painter->save(); painter->setBrush(QBrush(QColor(0, 255, 0, 255), Qt::SolidPattern)); painter->drawArc(-200, -200, 400, 400, -135*16, -270*16); //① painter->restore(); for(int i = 0; i < 180; i++) { //② painter->save(); painter->rotate(-225 + i * angle); if(i % 10 == 0) { painter->drawLine(180, 0, 200, 0); }else { painter->drawLine(190, 0, 200, 0); } painter->restore(); } painter->save(); for(int i = 0; i < 9; i++) { //③ int xTextPos = 180 * qCos((225 - i * 15)*3.14/180); int yTextPos = -180 * qSin((225 - i * 15)*3.14/180); painter->drawText(xTextPos+5, yTextPos+10, QString::number(i * 10)); painter->drawText(-xTextPos-25, yTextPos+10, QString::number((18 - i) * 10)); } painter->drawText(-10, -165, "90"); painter->restore(); }
① 绘制表盘的外形圆弧
② 绘制圆弧上的刻度信息
③ 绘制表盘外形的刻度旁边的数值,这里利用了圆的对称性
drawPointer函数
void Widget::drawPointer(QPainter *painter, qreal angle) { QPoint point[4] = { QPoint(0, 10), QPoint(-10, 0), QPoint(0, -170), QPoint(10, 0), }; painter->save(); QLinearGradient linear; //① linear.setStart(-200, -200); linear.setFinalStop(200, 200); linear.setColorAt(0, QColor(0, 255, 255, 0)); linear.setColorAt(1, QColor(0, 255, 255, 255)); painter->setPen(Qt::NoPen); painter->setBrush(linear); painter->drawPie(-200, -200, 400, 400, 225 * 16, -(angle * this->speed) * 16); painter->restore(); painter->save(); painter->setBrush(QBrush(QColor(0, 0, 0, 255), Qt::SolidPattern)); painter->rotate(-135 + this->speed * angle); painter->drawPolygon(point, 4); //② painter->restore(); }
① 根据当前速度计算扇形区域的大小,绘制渐变扇形
② 根据当前速度计算指针所要在的位置,旋转指针到该位置
drawSpeed函数
void Widget::drawSpeed(QPainter *painter) { painter->save(); painter->setPen(QColor("#0")); // 绘制速度 QFont font("Times", 10, QFont::Bold); font.setBold(true); font.setPixelSize(66); painter->setFont(font); painter->drawText(-60, 100, 120, 92, Qt::AlignCenter, QString::number(speed)); painter->restore(); }
drawSpeed函数在指定位置,使用指定颜色字体,显示当前速度
5、小结
到此,模拟仪表盘的实现讲解完毕了,这也算对这几天QT的学习做了一个小小的总结。从使用过程中可以感受到使用QT进行图形界面的设计,软件编写上还是比较灵活的。QT的GUI功能非常很强大,它提供的接口函数很多,但想要都用好的确很难,平时还需要多看看QT官方文档中的解释和提供的demo。
完整代码
#include "widget.h" #include <QPainter> #include <QBrush> #include <QLabel> #include <QTimerEvent> #include <QLinearGradient> #include <QFont> #include <QtMath> Widget::Widget(QWidget *parent) : QWidget(parent) { resize(800, 480); setWindowTitle("模拟仪表盘"); this->speed = 0; this->status = 0; this->time_id = this->startTimer(50); } Widget::~Widget() { } void Widget::paintEvent(QPaintEvent *event) { qreal angle; angle = (qreal)270 / (180-1); QPainter painter(this); painter.translate(width()/2, height()/2); drawFrame(&painter, angle); drawPointer(&painter, angle); drawSpeed(&painter); } void Widget::drawFrame(QPainter *painter, qreal angle) { painter->save(); painter->setBrush(QBrush(QColor(0, 255, 0, 255), Qt::SolidPattern)); painter->drawArc(-200, -200, 400, 400, -135*16, -270*16); painter->restore(); for(int i = 0; i < 180; i++) { painter->save(); painter->rotate(-225 + i * angle); if(i % 10 == 0) { painter->drawLine(180, 0, 200, 0); }else { painter->drawLine(190, 0, 200, 0); } painter->restore(); } painter->save(); for(int i = 0; i < 9; i++) { int xTextPos = 180 * qCos((225 - i * 15)*3.14/180); int yTextPos = -180 * qSin((225 - i * 15)*3.14/180); painter->drawText(xTextPos+5, yTextPos+10, QString::number(i * 10)); painter->drawText(-xTextPos-25, yTextPos+10, QString::number((18 - i) * 10)); } painter->drawText(-10, -165, "90"); painter->restore(); } void Widget::drawPointer(QPainter *painter, qreal angle) { QPoint point[4] = { QPoint(0, 10), QPoint(-10, 0), QPoint(0, -170), QPoint(10, 0), }; painter->save(); QLinearGradient linear; linear.setStart(-200, -200); linear.setFinalStop(200, 200); linear.setColorAt(0, QColor(0, 255, 255, 0)); linear.setColorAt(1, QColor(0, 255, 255, 255)); painter->setPen(Qt::NoPen); painter->setBrush(linear); painter->drawPie(-200, -200, 400, 400, 225 * 16, -(angle * this->speed) * 16); painter->restore(); painter->save(); painter->setBrush(QBrush(QColor(0, 0, 0, 255), Qt::SolidPattern)); painter->rotate(-135 + this->speed * angle); painter->drawPolygon(point, 4); painter->restore(); } void Widget::drawSpeed(QPainter *painter) { painter->save(); painter->setPen(QColor("#0")); // 绘制速度 QFont font("Times", 10, QFont::Bold); font.setBold(true); font.setPixelSize(66); painter->setFont(font); painter->drawText(-60, 100, 120, 92, Qt::AlignCenter, QString::number(speed)); painter->restore(); } void Widget::timerEvent(QTimerEvent *e) { int timerId = e->timerId(); if(this->time_id == timerId) { if(this->status == 0) { this->speed += 1; if(this->speed >= 180) this->status = 1; }else { this->speed -= 1; if(this->speed <= 0) this->status = 0; } this->update(); } }
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); void paintEvent(QPaintEvent *event); void timerEvent(QTimerEvent *e); private: void drawFrame(QPainter *painter, qreal angle); void drawPointer(QPainter *painter, qreal angle); void drawSpeed(QPainter *painter); int speed; int time_id; int status; }; #endif // WIDGET_H
#include "widget.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); Widget w; w.show(); return a.exec(); }