Qt自定义控件之仪表盘的完整实现

简介: Qt自定义控件之仪表盘的完整实现

概述


基于QT的仪表盘有很多种办法,比如使用QWT,ChartDirector 或H5混合的echart组件,或者基于QT的绘图功能绘制,或者基于美工提供的图片的基础上增加动态效果。然而搞明白QT自定义控件的绘图后,这种实现是最简单的。且定制度高,想要什么效果就可以自己绘制个。这里介绍下Qt自定义控件之仪表盘的完整实现。


效果预览


以下是三种不同的手绘实现,网上的其他文章有的只提供效果或代码片段,这里附上完整能用的源码。最后再对其中的一个的实现做详细的原理实现和细节介绍。



源码实现


这两种实现的完整源码如下:


#ifndef MYDIAL_H
#define MYDIAL_H
#include <QWidget>
class MyDial : public QWidget
{
  Q_OBJECT
public:
    MyDial(QWidget *parent = 0);
    ~MyDial();
    void setValue(double val);
protected:
  void paintEvent(QPaintEvent *);
  void drawCrown(QPainter *painter);
  void drawBackground(QPainter *painter);
  void drawScale(QPainter *painter);
  void drawScaleNum(QPainter *painter);
  void drawTitle(QPainter *painter);
  void drawIndicator(QPainter *painter);
  void drawNumericValue(QPainter *painter);
private:
  QColor m_background;
  QColor m_foreground;
  int m_maxValue;
  int m_minValue;
  int m_startAngle;
  int m_endAngle;
  int m_scaleMajor;
  int m_scaleMinor;
  double m_value;
  int m_precision;
  QTimer *m_updateTimer;
  QString m_units;
  QString m_title;
public Q_SLOTS:
  void UpdateAngle();
private:
};
#endif // MYDIAL_H


#include "mydial.h"
#include <QPainter>
// 构造函数
MyDial::MyDial(QWidget *parent){
  m_background = Qt::black;
    m_foreground = Qt::white;
  m_startAngle = 60;
  m_endAngle = 60;
  m_scaleMajor = 10;
  m_minValue = 0;
  m_maxValue = 100;
  m_scaleMajor = 10;//分度
  m_scaleMinor = 10;
    m_units = "L/min";
    m_title = "Water flow";
  m_precision = 0;
  m_value = 0;
    setWindowFlags(Qt::FramelessWindowHint);//无窗体
    setAttribute(Qt::WA_TranslucentBackground);//背景透明
    //resize(400, 400);
}
MyDial::~MyDial()
{
}
void MyDial::setValue(double val)
{
    m_value = val;
}
//绘制表冠 
void MyDial::drawCrown(QPainter *painter)
{ 
    painter->save();
    int radius = 100;
    QLinearGradient lg1(0, -radius, 0, radius);
    lg1.setColorAt(0, Qt::white); //设置渐变的颜色和路径比例
    lg1.setColorAt(1, Qt::gray); //只是粗略的颜色,具体的可以参考RGB颜色查询对照表
    painter->setBrush(lg1); // 创建QBrush对象,把这个渐变对象传递进去:
    painter->setPen(Qt::NoPen); //边框线无色
    painter->drawEllipse(-radius, -radius, radius << 1, radius << 1);
    painter->setBrush(m_background = Qt::black);
    painter->drawEllipse(-92, -92, 184, 184);
    painter->restore();
}
 //绘制刻度数字 
void MyDial::drawScaleNum(QPainter *painter)
{ 
    painter->save();
    painter->setPen(m_foreground);
    //m_startAngle是起始角度,m_endAngle是结束角度,m_scaleMajor在一个量程中分成的刻度数
    double startRad = ( 270-m_startAngle) * (3.14 / 180);
    double deltaRad = (360 - m_startAngle - m_endAngle) * (3.14 / 180) / m_scaleMajor;
    double sina,cosa;
    int x, y;
    QFontMetricsF fm(this->font());
    double w, h, tmpVal;
    QString str;
    for (int i = 0; i <= m_scaleMajor; i++)
    {
        sina = sin(startRad - i * deltaRad);
        cosa = cos(startRad - i * deltaRad);
       tmpVal = 1.0 * i *((m_maxValue - m_minValue) / m_scaleMajor) + m_minValue;
       // tmpVal = 50;
        str = QString( "%1" ).arg(tmpVal);  //%1作为占位符   arg()函数比起 sprintf()来是类型安全的
        w = fm.size(Qt::TextSingleLine,str).width();
        h = fm.size(Qt::TextSingleLine,str).height();
        x = 82 * cosa - w / 2;
        y = -82 * sina + h / 4;
        painter->drawText(x, y, str); //函数的前两个参数是显示的坐标位置,后一个是显示的内容,是字符类型""
    }
    painter->restore();
}
// 绘制刻度线
void MyDial::drawScale(QPainter *painter) //绘制刻度线
{ 
    painter->save();
    painter->rotate(m_startAngle);
    int steps = (m_scaleMajor * m_scaleMinor); //相乘后的值是分的份数
    double angleStep = (360.0 - m_startAngle - m_endAngle) / steps; //每一个份数的角度
    // painter->setPen(m_foreground); //m_foreground是颜色的设置
    // QPen pen = painter->pen(); //第一种方法
    QPen pen ;
    pen.setColor(Qt::green); //推荐使用第二种方式
    for (int i = 0; i <= steps; i++)
    {
    if (i % m_scaleMinor == 0)//整数刻度显示加粗
    {
    pen.setWidth(1); //设置线宽
    painter->setPen(pen); //使用面向对象的思想,把画笔关联上画家。通过画家画出来
      painter->drawLine(0, 62, 0, 72); //两个参数应该是两个坐标值
       }
       else
       {
           pen.setWidth(0);
           painter->setPen(pen);
           painter->drawLine(0, 67, 0, 72);
       }
       painter->rotate(angleStep);
    }
    painter->restore();
}
void MyDial::drawTitle(QPainter *painter)
{ 
    painter->save();
    painter->setPen(m_foreground);
    //painter->setBrush(m_foreground);
    QString str(m_title); //显示仪表的功能
    QFontMetricsF fm(this->font());
    double w = fm.size(Qt::TextSingleLine,str).width();
    painter->drawText(-w / 2, -30, str);
    painter->restore();
}
// 显示的单位,与数值
void MyDial::drawNumericValue(QPainter *painter)
{ 
    QString str = QString("%1 %2").arg(m_value, 0, 'f', m_precision).arg(m_units);
    QFontMetricsF fm(font());
    double w = fm.size(Qt::TextSingleLine,str).width();
    painter->setPen(m_foreground);
    painter->drawText(-w / 2, 42, str);
}
void MyDial::UpdateAngle()
{
    update();
}
// 绘制表针,和中心点
void MyDial::drawIndicator(QPainter *painter)
{
  painter->save();
  QPolygon pts;
  pts.setPoints(3, -2, 0, 2, 0, 0, 60); /* (-2,0)/(2,0)/(0,60) *///第一个参数是 ,坐标的个数。后边的是坐标
  painter->rotate(m_startAngle);
  double degRotate = (360.0 - m_startAngle - m_endAngle) / (m_maxValue - m_minValue)*(m_value - m_minValue);
  //画指针
  painter->rotate(degRotate);  //顺时针旋转坐标系统
  QRadialGradient haloGradient(0, 0, 60, 0, 0);  //辐射渐变
  haloGradient.setColorAt(0, QColor(60, 60, 60));
  haloGradient.setColorAt(1, QColor(160, 160, 160)); //灰
  painter->setPen(Qt::white); //定义线条文本颜色  设置线条的颜色
  painter->setBrush(haloGradient);//刷子定义形状如何填满 填充后的颜色
  painter->drawConvexPolygon(pts); //这是个重载函数,绘制多边形。
  painter->restore();
  //画中心点
  QColor niceBlue(150, 150, 200);
  QConicalGradient coneGradient(0, 0, -90.0);  //角度渐变
  coneGradient.setColorAt(0.0, Qt::darkGray);
  coneGradient.setColorAt(0.2, niceBlue);
  coneGradient.setColorAt(0.5, Qt::white);
  coneGradient.setColorAt(1.0, Qt::darkGray);
  painter->setPen(Qt::NoPen);  //没有线,填满没有边界
  painter->setBrush(coneGradient);
  painter->drawEllipse(-5, -5, 10, 10);
}
// 重绘函数
void MyDial ::paintEvent(QPaintEvent *)
{ 
    int width=this->width();
    int height=this->height();
    QPainter painter(this);//一个类中的this表示一个指向该类自己的指针
    painter.setRenderHint(QPainter::Antialiasing);  /* 使用反锯齿(如果可用) */
    painter.translate(width/2, height/2);   /* 坐标变换为窗体中心 */
    int side = qMin(width, height);
    painter.scale(side / 200.0, side / 200.0);      /* 比例缩放 */
   drawCrown(&painter);                                 /* 画表盘边框 */
   drawScaleNum(&painter);                          /* 画刻度数值值 */
   drawScale(&painter);                                 /* 画刻度线 */
   drawTitle(&painter);                                 /* 画单位 */
   drawNumericValue(&painter);                      /* 画数字显示 */
   drawIndicator(&painter);                             /* 画表针 */
}


另一个的实现,文末有引用,感谢


#ifndef GAUGEPANEL_H
#define GAUGEPANEL_H
#include <QWidget>
#include <QPropertyAnimation>
#include <QtMath>
#include <QPainter>
#ifdef quc
#if (QT_VERSION < QT_VERSION_CHECK(5,7,0))
#include <QtDesigner/QDesignerExportWidget>
#else
#include <QtUiPlugin/QDesignerExportWidget>
#endif
class QDESIGNER_WIDGET_EXPORT GaugePanel : public QWidget
#else
class GaugePanel : public QWidget
#endif
{
Q_OBJECT
Q_PROPERTY(double value READ getValue WRITE setValue)
Q_PROPERTY(int hShearValue READ getHShearValue WRITE setHShearValue)
Q_PROPERTY(int vShearValue READ getVShearValue WRITE setVShearValue)
Q_PROPERTY(double radiusInner READ getRadiusInner WRITE setRadiusInner)
Q_PROPERTY(double radiusOuter READ getRadiusOuter WRITE setRadiusOuter)
Q_PROPERTY(double radiusHalo READ getRadiusHalo WRITE setRadiusHalo)
Q_PROPERTY(QColor colorOuterFrame READ getColorOuterFrame WRITE setColorOuterFrame)
Q_PROPERTY(QColor colorInnerStart READ getColorInnerStart WRITE setColorInnerStart)
Q_PROPERTY(QColor colorInnerEnd READ getColorInnerEnd WRITE setColorInnerEnd)
Q_PROPERTY(QColor colorOuterStart READ getColorOuterStart WRITE setColorOuterStart)
Q_PROPERTY(QColor colorOuterEnd READ getColorOuterEnd WRITE setColorOuterEnd)
Q_PROPERTY(QColor colorHaloStart READ getColorHaloStart WRITE setColorHaloStart)
Q_PROPERTY(QColor colorHaloEnd READ getColorHaloEnd WRITE setColorHaloEnd)
public:
explicit GaugePanel(QWidget *parent = nullptr);
~GaugePanel();
protected:
void paintEvent(QPaintEvent *);
private:
void drawOuterGradient(QPainter *painter);
void drawInnerGradient(QPainter *painter);
void drawOuterHalo(QPainter *painter);
void drawScale(QPainter *painter);
void drawScaleNum(QPainter *painter);
void drawPointer(QPainter *painter);
void drawPointerSector(QPainter *painter);
void drawValue(QPainter *painter);
void drawUnit(QPainter *painter);
private:
double value;                   //目标值
int hShearValue, vShearValue;//H、V扭曲值
double radiusInner;             //渐变内圈内半径
double radiusOuter;             //渐变外圈内半径
double radiusHalo;              //光晕内半径
QColor colorOuterFrame;         //表盘外边框颜色
QColor colorInnerStart;         //渐变内圈起始颜色
QColor colorInnerEnd;           //渐变内圈结束颜色
QColor colorOuterStart;         //渐变外圈起始颜色
QColor colorOuterEnd;           //渐变外圈结束颜色
QColor colorHaloStart;          //光晕起始颜色
QColor colorHaloEnd;            //光晕结束颜色
QPropertyAnimation *hShearAnimation, *vShearAnimation;
public:
double getValue()               const;
int    getHShearValue()         const;
int    getVShearValue()         const;
double getRadiusInner()         const;
double getRadiusOuter()         const;
double getRadiusHalo()          const;
QColor getColorOuterFrame()     const;
QColor getColorInnerStart()     const;
QColor getColorInnerEnd()       const;
QColor getColorOuterStart()     const;
QColor getColorOuterEnd()       const;
QColor getColorHaloStart()      const;
QColor getColorHaloEnd()        const;
void setValue(int value);
void setValue(double value);
void setHShearValue(int value);
void setVShearValue(int value);
//表盘外边框颜色
void setColorOuterFrame(QColor color);
//内层渐变区半径
void setRadiusInner(int radius);
void setRadiusInner(double radius);
//外层渐变区半径
void setRadiusOuter(int radius);
void setRadiusOuter(double radius);
//外层光晕区半径
void setRadiusHalo(int radius);
void setRadiusHalo(double radius);
//内层渐变颜色
void setColorInnerStart(QColor color);
void setColorInnerEnd(QColor color);
//外层渐变颜色
void setColorOuterStart(QColor color);
void setColorOuterEnd(QColor color);
//光晕颜色
void setColorHaloStart(QColor color);
void setColorHaloEnd(QColor color);
void startShearAnimal(int duration, int hShearValue, int vShearValue);
public slots:
void updateValue(double value);
Q_SIGNALS:
    void valueChanged(qreal value);
};
#endif // GaugePanel_H


#include "gaugepanel.h"
GaugePanel::GaugePanel(QWidget *parent) : QWidget(parent)
{
    value = hShearValue = vShearValue = 0.0;
    radiusInner = 65.0;
    radiusOuter = 76.25;
    radiusHalo = 87.5;
    colorOuterFrame = QColor(50, 154, 255, 250);
    colorInnerStart = QColor(50, 154, 255, 180);
    colorInnerEnd = QColor(50, 154, 255, 70);
    colorOuterStart = QColor(50, 154, 255, 150);
    colorOuterEnd = QColor(50, 154, 255, 200);
    colorHaloStart = QColor(100, 180, 255, 80);
    colorHaloEnd = QColor(30, 80, 120, 20);
    hShearAnimation = new QPropertyAnimation(this, "hShearValue");
    vShearAnimation = new QPropertyAnimation(this, "vShearValue");
    //setWindowFlags(Qt::FramelessWindowHint);//无窗体
    //setAttribute(Qt::WA_TranslucentBackground);//背景透明
}
GaugePanel::~GaugePanel()
{
    hShearAnimation->stop();
    vShearAnimation->stop();
    delete hShearAnimation;
    delete vShearAnimation;
}
void GaugePanel::paintEvent(QPaintEvent *)
{
    int width = this->width();
    int height = this->height();
    int side = qMin(width, height);
    //绘制准备工作,启用反锯齿,平移坐标轴中心,等比例缩放
    QPainter painter(this);
    painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
    painter.translate(width / 2, height / 2);
    painter.scale(side / 215.0, side / 215.0);
    painter.shear(double(hShearValue/100.0f), double(vShearValue/100.0f));
    //内层渐变
    drawInnerGradient(&painter);
    //外层渐变
    drawOuterGradient(&painter);
    //外层光晕
    drawOuterHalo(&painter);
    //刻度线
    drawScale(&painter);
    //刻度值
    drawScaleNum(&painter);
    //绘制指针
    drawPointer(&painter);
    //绘制指针扇形
    drawPointerSector(&painter);
    //绘制值
    drawValue(&painter);
    //绘制单位
    drawUnit(&painter);
}
void GaugePanel::drawOuterGradient(QPainter *painter)
{
    if(radiusHalo <= radiusOuter)
        return;
    painter->save();
    QRectF rectangle(0-radiusHalo, 0-radiusHalo, radiusHalo*2, radiusHalo*2);
    QPen framePen(colorOuterFrame);
    framePen.setWidthF(1.5f);
    painter->setPen(framePen);
    painter->drawEllipse(rectangle);
    painter->setPen(Qt::NoPen);
    QPainterPath smallCircle;
    QPainterPath bigCircle;
    float radius = radiusOuter;
    smallCircle.addEllipse(-radius, -radius, radius * 2, radius * 2);
    radius += (radiusHalo - radiusOuter);
    bigCircle.addEllipse(-radius, -radius, radius * 2, radius * 2);
    //大圆抛去小圆部分
    QPainterPath gradientPath = bigCircle - smallCircle;
    QRadialGradient gradient(0, 0, radius, 0, 0);
    //gradient.setSpread(QGradient::ReflectSpread);
    gradient.setColorAt(0.85, colorOuterStart);
    gradient.setColorAt(0.98, colorOuterEnd);
    painter->setBrush(gradient);
    painter->drawPath(gradientPath);
    painter->restore();
}
void GaugePanel::drawInnerGradient(QPainter *painter)
{
    if(radiusOuter <= radiusInner)
        return;
    painter->save();
    painter->setPen(Qt::NoPen);
    QPainterPath smallCircle;
    QPainterPath bigCircle;
    float radius = radiusInner;
    smallCircle.addEllipse(-radius, -radius, radius * 2, radius * 2);
    radius += (radiusOuter - radiusInner);
    bigCircle.addEllipse(-radius, -radius, radius * 2, radius * 2);
    //大圆抛去小圆部分
    QPainterPath gradientPath = bigCircle - smallCircle;
    QRadialGradient gradient(0, 0, radius, 0, 0);
    //gradient.setSpread(QGradient::ReflectSpread);
    gradient.setColorAt(0.7, colorInnerStart);
    gradient.setColorAt(1, colorInnerEnd);
    painter->setBrush(gradient);
    painter->drawPath(gradientPath);
    painter->restore();
}
void GaugePanel::drawOuterHalo(QPainter *painter)
{
    painter->save();
    painter->setPen(Qt::NoPen);
    QPainterPath smallCircle;
    QPainterPath bigCircle;
    float radius = radiusHalo;
    smallCircle.addEllipse(-radius, -radius, radius * 2, radius * 2);
    radius += (100.0 - radiusHalo);
    bigCircle.addEllipse(-radius, -radius, radius * 2, radius * 2);
    //大圆抛去小圆部分
    QPainterPath gradientPath = bigCircle - smallCircle;
    QRadialGradient gradient(0, 0, 100, 0, 0);
    gradient.setSpread(QGradient::ReflectSpread);
    gradient.setColorAt(radiusHalo/100, colorHaloStart);
    gradient.setColorAt(1, colorHaloEnd);
    painter->setBrush(gradient);
    painter->drawPath(gradientPath);
    painter->restore();
}
void GaugePanel::drawScale(QPainter *painter)
{
    float radius = 85;
    painter->save();
    painter->setPen(QColor(255, 255, 255));
    painter->rotate(30);
    int steps = (30);
    double angleStep = (360.0 - 60) / steps;
    QPen pen = painter->pen();
    pen.setCapStyle(Qt::RoundCap);
    for (int i = 0; i <= steps; i++) {
        if (i % 3 == 0) {
            pen.setWidthF(1.5);
            painter->setPen(pen);
            QLineF line(0.0f, radius - 8.0f, 0.0f, radius);
            painter->drawLine(line);
        } else {
            pen.setWidthF(0.5);
            painter->setPen(pen);
            QLineF line(0.0f, radius - 3.0f, 0.0f, radius);
            painter->drawLine(line);
        }
        painter->rotate(angleStep);
    }
    painter->restore();
}
void GaugePanel::drawScaleNum(QPainter *painter)
{
    float radius = 95.0f;
    painter->save();
    painter->setPen(QColor(255, 255, 255));
    double startRad = (330 - 90) * (M_PI / 180);
    double deltaRad = (300) * (M_PI / 180) / 10;
    for (int i = 0; i <= 10; i++) {
        double sina = sin(startRad - i * deltaRad);
        double cosa = cos(startRad - i * deltaRad);
        double value = 1.0 * i * ((30) / 10);//刻度值范围
        QString strValue = QString("%1").arg((double)value, 0, 'f', 0);
        double textWidth = fontMetrics().width(strValue);
        double textHeight = fontMetrics().height();
        int x = radius * cosa - textWidth / 2;
        int y = -radius * sina + textHeight / 4;
        painter->drawText(x, y, strValue);
    }
    painter->restore();
}
void GaugePanel::drawPointer(QPainter *painter)
{
    painter->save();
    float radius = 83.0;
    painter->rotate(30+int(value*10));
    QPen pen = painter->pen();
    pen.setWidthF(1.0);
    pen.setColor(QColor(50, 154, 255, 200));
    painter->setPen(pen);
    QLineF line(0.0f, 0.0f, 0.0f, radius);
    painter->drawLine(line);
    painter->restore();
}
void GaugePanel::drawPointerSector(QPainter *painter)
{
    float radius = 87.5f;
    painter->save();
    painter->setPen(Qt::NoPen);
    QRectF rect(-radius, -radius, radius * 2, radius * 2);
    painter->setBrush(QColor(50, 154, 255, 50));
    painter->drawPie(rect, -120*16, -value*16*10);
    painter->restore();
}
void GaugePanel::drawValue(QPainter *painter)
{
    int radius = 100;
    painter->save();
    painter->setPen(QColor(255, 255, 255));
    painter->setFont(QFont("Arial", 22, 22, true));
    QRectF textRect(-radius, -radius, radius * 2, radius * 2);
    QString strValue = QString("%1").arg((double)value, 0, 'f', 0);
    painter->drawText(textRect, Qt::AlignCenter, strValue);
    painter->restore();
}
void GaugePanel::drawUnit(QPainter *painter)
{
    int radius = 100;
    painter->save();
    painter->setPen(QColor(255, 255, 255));
    painter->setFont(QFont("Arial", 9, -1, true));
    QRectF textRect(-radius, -radius+20, radius * 2, radius * 2);
    painter->drawText(textRect, Qt::AlignCenter, "km/h");
    painter->restore();
}
double GaugePanel::getValue() const
{
    return this->value;
}
int GaugePanel::getHShearValue() const
{
    return this->hShearValue;
}
int GaugePanel::getVShearValue() const
{
    return this->vShearValue;
}
double GaugePanel::getRadiusInner() const
{
    return radiusInner;
}
double GaugePanel::getRadiusOuter() const
{
    return radiusOuter;
}
double GaugePanel::getRadiusHalo() const
{
    return radiusHalo;
}
QColor GaugePanel::getColorOuterFrame() const
{
    return colorOuterFrame;
}
QColor GaugePanel::getColorInnerStart() const
{
    return colorInnerStart;
}
QColor GaugePanel::getColorInnerEnd() const
{
    return colorInnerEnd;
}
QColor GaugePanel::getColorOuterStart() const
{
    return colorOuterStart;
}
QColor GaugePanel::getColorOuterEnd() const
{
    return colorOuterEnd;
}
QColor GaugePanel::getColorHaloStart() const
{
    return colorHaloStart;
}
QColor GaugePanel::getColorHaloEnd() const
{
    return colorHaloEnd;
}
void GaugePanel::setValue(int value)
{
    setValue(double(value));
}
void GaugePanel::setValue(double value) {
    updateValue(value);
}
void GaugePanel::setHShearValue(int value)
{
    if(value > 100 || value < -100)
        return;
    this->hShearValue = value;
    update();
}
void GaugePanel::setVShearValue(int value)
{
    if(value > 100 || value < -100)
        return;
    this->vShearValue = value;
    update();
}
void GaugePanel::setColorOuterFrame(QColor color)
{
    colorOuterFrame = color;
}
void GaugePanel::setRadiusInner(int radius)
{
    setRadiusInner(double(radius));
}
void GaugePanel::setRadiusInner(double radius)
{
    if(radius >= 0.0f && radius < 100.0f){
        radiusInner = radius;
        update();
    }
}
void GaugePanel::setRadiusOuter(int radius)
{
    setRadiusOuter(double(radius));
}
void GaugePanel::setRadiusOuter(double radius)
{
    if(radius > 0.0f && radius < 100.0f){
        radiusOuter = radius;
        update();
    }
}
void GaugePanel::setRadiusHalo(int radius)
{
    setRadiusHalo(double(radius));
}
void GaugePanel::setRadiusHalo(double radius)
{
    if(radius > 0.0f && radius < 100.0f){
        radiusHalo = radius;
        update();
    }
}
void GaugePanel::setColorInnerStart(QColor color)
{
    colorInnerStart = color;
}
void GaugePanel::setColorInnerEnd(QColor color)
{
    colorInnerEnd = color;
}
void GaugePanel::setColorOuterStart(QColor color)
{
    colorOuterStart = color;
}
void GaugePanel::setColorOuterEnd(QColor color)
{
    colorOuterEnd = color;
}
void GaugePanel::setColorHaloStart(QColor color)
{
    colorHaloStart = color;
}
void GaugePanel::setColorHaloEnd(QColor color)
{
    colorHaloEnd = color;
}
void GaugePanel::startShearAnimal(int duration, int hShearValue, int vShearValue)
{
    if(hShearValue == this->hShearValue && vShearValue == this->vShearValue){
        return;
    }
    if(hShearAnimation->state() != QPropertyAnimation::Stopped){
        hShearAnimation->stop();
    }
    if(vShearAnimation->state() != QPropertyAnimation::Stopped){
        vShearAnimation->stop();
    }
    hShearAnimation->setDuration(duration);
    hShearAnimation->setStartValue(this->hShearValue);
    hShearAnimation->setEndValue(hShearValue);
    hShearAnimation->start();
    vShearAnimation->setDuration(duration);
    vShearAnimation->setStartValue(this->vShearValue);
    vShearAnimation->setEndValue(vShearValue);
    vShearAnimation->start();
}
void GaugePanel::updateValue(double value)
{
    if(value > 30.0 || value < 0.0){
        return;
    }
    this->value = value;
    //update();
    this->update();
   // emit valueChanged(value);
}


原理分析


实现原理


以实现下图的模拟表盘为例,分析下实现的原理和细节。



表盘的实现是基于QT的QPainter类中的绘图方法,自定义实现一个QWidget控件。


将表盘分解析为3个组成部分。表盘的外形轮廓、指针和显示的当前速度的数值。


外形轮廓由一个圆弧和一些指示刻度组成,它的绘制肯定要使用QT中的画圆弧的函数、画线函数还有显示文本函数。


指针是一个不规则的多边形,它的绘制会用到QT中的绘制多边形的函数。


显示当前速度值比较简单些,直接使用显示文本函数绘制。


先有了静态部分的基础,再开始考虑指针的动态旋转过程和旋转过程中的渐变效果是如何实现的。


指针旋转的角度应该和当前的转速相互对应。当前转速改变时,会根据新的转速计算出当前指针位于什么角度的位置,然后可以调用QT的旋转角度函数让多边形指针旋转到这个位置。


旋转的渐变效果其实是通过绘制扇形实现的,要绘制扇形的角度和指针旋转的角度是一样的。


由于绘制的扇形的内部的着色采用了颜色的线性内插,所以不同的角度显示的颜色程度不同。


因此给人以渐变的效果。让转速自增1还是自减1,转速改变时调用函数让界面进行重新绘制。


我们知道了每个刻度线对应的角度和半径,我们就可以求出每条刻度线的起点和终点,公式如下:


int x = 圆心的横坐标 + 半径 * cos(角度 * π / 180);
int y = 圆心的纵坐标 + 半径 * sin(角度 * π / 180);


实现源码


#include "myspeed.h"
#include <QPainter>
#include <QBrush>
#include <QLabel>
#include <QTimerEvent>
#include <QLinearGradient>
#include <QFont>
#include <QtMath>
MySpeed::MySpeed(QWidget *parent)
    : QWidget(parent)
{
    //resize(800, 480);
    //setWindowTitle("test");
    m_foreground = Qt::black;
    speed = 0;
    status = 0;
    m_startAngle = 45;
    m_endAngle = 45;
    m_minValue = 0;
    m_maxValue = 100;
    m_scaleMajor = 10;//分度
    m_scaleMinor = 10;
    m_units = "L/min";
    m_title = "My Speed";
    m_precision = 0;
    m_value = 0;
    m_angle = (qreal)270/(m_maxValue-1);
    time_id = this->startTimer(50);
}
MySpeed::~MySpeed()
{
}
void MySpeed::paintEvent(QPaintEvent *event)
{
    int width=this->width();
    int height=this->height();
    QPainter painter(this);
    painter.translate(width/2, height/2);
    int side = qMin(width, height);
    painter.scale(side / 200.0, side / 200.0);      /* 比例缩放 */
    drawFrame(&painter);
    drawScale(&painter);                                 /* 画刻度线 */
    drawScaleNum(&painter);                          /* 画刻度数值值 */
    drawUnit(&painter);
    drawPointer(&painter);
    drawSpeed(&painter);
}
void MySpeed::drawFrame(QPainter *painter)
{
    painter->save();
    // 半径100
    int radius = 100;
    painter->setBrush(QBrush(QColor(0, 255, 0, 255), Qt::SolidPattern));
    painter->drawArc(-radius, -radius, radius<<1, radius<<1, -135*16, -270*16);
    painter->restore();
}
// 绘制刻度线
void MySpeed::drawScale(QPainter *painter)
{
    painter->save();
    painter->rotate(m_startAngle);
    int steps = (m_scaleMajor * m_scaleMinor); //相乘后的值是分的份数
    double angleStep = (360.0 - m_startAngle - m_endAngle) / steps; //每一个份数的角度
    // painter->setPen(m_foreground); //m_foreground是颜色的设置
    // QPen pen = painter->pen(); //第一种方法
    QPen pen ;
    pen.setColor(m_foreground); //推荐使用第二种方式
    for (int i = 0; i <= steps; i++)
    {
        if (i % m_scaleMinor == 0)//整数刻度显示加粗
        {
            pen.setWidth(1); //设置线宽
            painter->setPen(pen); //使用面向对象的思想,把画笔关联上画家。通过画家画出来
            painter->drawLine(0, 90, 0, 100); //两个参数应该是两个坐标值
        }
        else
        {
           pen.setWidth(0);
           painter->setPen(pen);
           painter->drawLine(0, 95, 0, 100);
        }
       painter->rotate(angleStep);
    }
    painter->restore();
}
// 绘制刻度
void MySpeed::drawScaleNum(QPainter *painter)
{
    painter->save();
    painter->setPen(m_foreground);
    //m_startAngle是起始角度,m_endAngle是结束角度,m_scaleMajor在一个量程中分成的刻度数
    double startRad = ( 270-m_startAngle) * (3.14 / 180);
    double deltaRad = (360 - m_startAngle - m_endAngle) * (3.14 / 180) / m_scaleMajor;
    double sina,cosa;
    int x, y;
    QFontMetricsF fm(this->font());
    double w, h, tmpVal;
    QString str;
    for (int i = 0; i <= m_scaleMajor; i++)
    {
        sina = sin(startRad - i * deltaRad);
        cosa = cos(startRad - i * deltaRad);
       tmpVal = 1.0 * i *((m_maxValue - m_minValue) / m_scaleMajor) + m_minValue;
       // tmpVal = 50;
        str = QString( "%1" ).arg(tmpVal);  //%1作为占位符   arg()函数比起 sprintf()来是类型安全的
        w = fm.size(Qt::TextSingleLine,str).width();
        h = fm.size(Qt::TextSingleLine,str).height();
        x = 82 * cosa - w / 2;
        y = -82 * sina + h / 4;
        painter->drawText(x, y, str); //函数的前两个参数是显示的坐标位置,后一个是显示的内容,是字符类型""
    }
    painter->restore();
}
void MySpeed::drawPointer(QPainter *painter)
{
    int radius = 100;
    QPoint point[4] = {
        QPoint(0, 10),
        QPoint(-10, 0),
        QPoint(0, -80),
        QPoint(10, 0),
    };
    painter->save();
    QLinearGradient linear;
    linear.setStart(-radius, -radius);
    linear.setFinalStop(radius<<1, radius<<1);
    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(-radius, -radius, radius<<1, radius<<1, 225 * 16, -(m_angle * this->speed) * 16);
    painter->restore();
    painter->save();
    painter->setBrush(QBrush(QColor(0, 0, 0, 255), Qt::SolidPattern));
    painter->rotate(-135 + this->speed * m_angle);
    painter->drawPolygon(point, 4);
    painter->restore();
}
void MySpeed::drawSpeed(QPainter *painter)
{
    painter->save();
    painter->setPen(QColor("#0"));
    //  绘制速度
    QFont font("Times", 10, QFont::Bold);
    font.setBold(true);
    font.setPixelSize(46);
    painter->setFont(font);
    painter->drawText(-60, 0, 120, 92, Qt::AlignCenter, QString::number(speed));
    painter->restore();
}
void MySpeed::drawUnit(QPainter *painter)
{
    QString str = QString("%1").arg(m_units);
    QFontMetricsF fm(font());
    double w = fm.size(Qt::TextSingleLine,str).width();
    painter->setPen(m_foreground);
    painter->drawText(-w / 2, 82, str);
}
void MySpeed::timerEvent(QTimerEvent *e)
{
    int timerId = e->timerId();
    if(this->time_id == timerId) {
        if(this->status == 0) {
            this->speed += 1;
            if(this->speed >= m_maxValue)
                this->status = 1;
        }else {
            this->speed -= 1;
            if(this->speed <= 0)
                this->status = 0;
        }
        this->update();
    }
}


#ifndef MYSPEED_H
#define MYSPEED_H
#include <QWidget>
class MySpeed : public QWidget
{
    Q_OBJECT
public:
    MySpeed(QWidget *parent = nullptr);
    ~MySpeed();
    void paintEvent(QPaintEvent *event);
    void timerEvent(QTimerEvent *e);
private:
    void drawFrame(QPainter *painter);
    void drawScale(QPainter *painter);
    void drawScaleNum(QPainter *painter);
    void drawPointer(QPainter *painter);
    void drawSpeed(QPainter *painter);
    void drawUnit(QPainter *painter);
    int speed;
    int time_id;
    int status;
    qreal m_angle;
    QColor m_foreground;
    int m_maxValue;
    int m_minValue;
    int m_startAngle;
    int m_endAngle;
    int m_scaleMajor;
    int m_scaleMinor;
    double m_value;
    int m_precision;
    //QTimer *m_updateTimer;
    QString m_units;
    QString m_title;
};
#endif // MYSPEED_H


引用


Qt控件:仪表盘_EEer!的博客-CSDN博客_qt 仪表盘


Qt:绘制仪表盘_Francis_Ye的博客-CSDN博客_qt 仪表盘


Qt总结之八:绘制仪表盘_ooMelloo的博客-CSDN博客_qt 仪表盘


qt实现一个简单的仪表盘_黑色肥猫的博客-CSDN博客_qt 仪表盘


QT绘制简易表盘_我不是萧海哇~~~~的博客-CSDN博客_qt绘制仪表盘


学习QT之自定义控件入门学习#-仪表盘_贝勒里恩的博客-CSDN博客_qt仪表盘


Qt:汽车仪表盘控件_苏三爱吃炸酱面的博客-CSDN博客_qt汽车仪表盘


QT样式表之径向渐变(qradialgradient)_Allen Roson的博客-CSDN博客


学习QT之QT绘图原理详解_贝勒里恩的博客-CSDN博客_qt 绘图原理


Qt自定义控件 -- 仪表盘01_flist的博客-CSDN博客_qt 仪表盘

相关文章
|
编解码 开发工具 C语言
Qt编写自定义控件10-云台仪表盘
一、前言 做过安防视频监控的同学都清楚,在视频监控系统软件上都可以看到一个云台控制区域,可以对球机进行下下左右等八个方位的运动控制,还可以进行复位,一般都是美工作图好,然后贴图的形式加入到软件中,好处是程序简单,界面美工,主要取决于美工的美图能力,缺点是对于各种分辨率的适应性稍微差点,需要不同的图片切图贴图,除非默认做好的是大图自适应看不出差别,可能大部分人所在的公司都是小公司,一般美工人员比较少甚至没有,都需要程序员一人负责,甚至一开始就要考虑到各种分辨率的应用场景以及后期可能的换肤换色等。
1348 0
|
程序员 C语言
Qt编写自定义控件49-飞机仪表盘
一、前言 飞行仪表是测定和表示飞机数据的工具,飞机中必不可少的一部分,飞行员根据飞行仪表表示的数据才能正确地做出判断。一般飞机仪表包括高度表+空速表+垂直速率表+姿态仪+航向指示表+转弯协调表。这次要绘制的是其中的姿势仪,显示飞机相对于地平线的姿态,看姿态仪,飞行员能判断飞机姿态为偏左偏右,及偏上和偏下。
1614 0
|
9月前
|
C++ 计算机视觉 Python
Qt+C++自定义控件仪表盘动画仿真
这篇博客针对< Qt+C++自定义控件仪表盘动画仿真>编写代码,代码整洁,规则,易读。 应用推荐首选。
72 0
Qt | 实现一个简单的可以转动的仪表盘
通过paint实现一个可以转动的仪表盘。
205 0
|
程序员 C语言
Qt编写自定义控件52-颜色下拉框
一、前言 这个控件写了很久了,元老级别的控件之一,开发之初主要是自己的好几个项目要用到,比如提供一个颜色下拉框设置对应的曲线或者时间颜色,视频监控项目中经常用到的OSD标签设置,这个控件的难度系数接近0,初学者都会,其实网上搜索也很多人提供了绘制的方法,就是枚举QColor::colorNames()拿到所有的内置的颜色,然后生成对应的图片作为icon设置到下拉框的item中去,对应icon的宽高由控件本身的宽高决定,本控件继承自qcombobox控件,完全保留了该控件的所有特性,同时新增了颜色改变信号,以便用户使用。
1300 0
|
程序员 C语言
Qt编写自定义控件51-可输入仪表盘
一、前言 这个控件是近期定制的控件,还是比较实用的控件之一,用户主要是提了三点需求,一点是切换焦点的时候控件放大突出显示,一点是可直接输入或者编辑值,还有一点是支持上下键及翻页键和鼠标滚轮来动态修改值,类似于qspinbox控件。
1664 0
|
程序员 C语言
Qt编写自定义控件50-迷你仪表盘
一、前言 这个控件取名叫迷你仪表盘,是以为该控件可以缩小到很小很小的区域显示,非常适合小面积区域展示仪表数据使用,还可以手动触摸调节进度,是我个人觉得最漂亮小巧的一个控件。初次看到类似的控件是在一个音乐视频编辑软件中,用来展示左通道右通道音量等,有非常多的类似的迷你仪表盘在整个软件系统中,用户可以直接鼠标滑动调节,以最小的占用区域展示最大的信息,漂亮!本控件还拓展了可以左右等分显示,比如中间的值是0,左侧就是低于0的值区域,右侧就是大于0的值区域,进度不一样展示,支持左右旋转角度设置以及各种颜色的设置。
885 0
|
程序员 开发工具 C语言
Qt编写自定义控件44-天气仪表盘
一、前言 天气仪表盘控件是所有控件中唯一一个使用了svg矢量图的控件,各种天气图标采用的矢量图,颜色变换采用动态载入svg的内容更改生成的,其实也可以采用图形字体来做,本次控件为了熟悉下svg在Qt中的使用,才采用的svg来绘制。
1514 0
|
程序员 开发工具 C语言
Qt编写自定义控件47-面板区域控件
一、前言 在很多web网页上,经常可以看到一个设备对应一个面板,或者某种同等类型的信息全部放在一个面板上,该面板还可以拖来拖去的,这个控件首次用在智能访客管理平台中,比如身份证信息一个面板,访客信息一个面板,被访人信息一个面板,这样相当于分类展示了,还提供了对应的标题栏有文字显示,这个控件的使用场景也是非常多,还有个子标题可以设置,拓展了报警闪烁的接口。
1196 0