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

Posted 特立独行的猫a

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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自定义控件之仪表盘的完整实现的主要内容,如果未能解决你的问题,请参考以下文章

Qt编写自定义控件51-可输入仪表盘

Qt编写自定义控件21-圆弧仪表盘

Qt自定义精美的仪表盘控件(汽车仪表指南针雷达摇杆)

Qt自定义精美的仪表盘控件(汽车仪表指南针雷达摇杆)

32.QT-制作最强电压电阻表盘,可以自定义颜色,图标,文字标签(也可以用来当其它表盘)

C# wpf 如何实现自定义控件,布局时,大小发生变化,内部绘制的曲线跟随变化?