Qt自绘汽车仪表盘-1

一、效果图

在这里插入图片描述
二、效果图拆解
根据效果图上显示,最外层一共是13个小点,这些小点有点像子弹头一样,头部是尖的,底部是平的。靠近一层是表盘码值表,数字是的底部朝向表盘圆心。再近一层是一个渐变圆环,颜色有黑色渐变成白蓝色。里面是121个刻度线,最里面是长刻度线和短刻度线,这些刻度线都是头尖底圆。最后内部正上方是绘制的文字,表盘中的指针也是头头尖底圆,指向0值。
其中主题颜色为蓝白色,指针颜色为暗红色。
三、绘制过程拆解
①首先是要解决里面图形(子弹头和数字)旋转的问题,如果直接计算旋转之后的图形再绘制,可能真的比较麻烦,不过三角函数比较优秀的人可以尝试直接旋转图形再绘制。这里采用的方法是使用QPainter自带的旋转绘图坐标的方式,先将坐标旋转之后,绘制图像,再将坐标还原回来,结果图看上去就是绘制的图形被旋转了。
②首先绘制最外层的13个子弹头,这些子弹头不是规则的图形(不是正方形,长方形之类的),但是可以用一个长方形+一个三角形构成,如下图所示:

在这里插入图片描述
这里绘制的是等比例放大的图像,将这个图像缩小之后,看上去菱角不那么明显时,就会误以为是圆角的(实际是眼睛欺骗了大脑的结果,当初为了实现菱角圆滑,真的费了很多心思没结果)。
这个图形虽然可以用正方形+三角形的方式构成,但是在绘图的时候比较麻烦,需要绘制两次,或者可以用QPainterPath计算两个图形的并集,但是都比较麻烦,不如直接用QPainterPath将路径添加在集合中,一次绘制即可。
③绘制13个数字码值表。这里也是先将坐标旋转之后,绘制数字,最后还原坐标。
④绘制渐变圆环。这里没有使用绘制两个扇形圆的方式(内部绘制一个遮罩圆),这里是先将圆环的路径计算出来,然后直接绘制路径所在的区域。
⑤绘制内部121个短刻度线。这里的效果图上看着短刻度线头尖底圆,这里和绘制子弹头相同,实际也是因为眼睛欺骗了大脑,实际这个子弹头放大之后是下面这样的:

在这里插入图片描述
实际上就是两个等腰梯形合并在一起的,由于效果图上点线距离太小,所以看上去就像是圆的。
⑥绘制长短刻度线。绘制方法和上面绘制短刻度线异样,只是梯形的长度不一样。
⑦绘制表盘上的文字。这里和绘制表盘数字一样。
⑧绘制表盘指针。这里由于指针图像比较大,不能再用梯形的方式绘制指针底部,所以这里指针底部是一个半圆。
四、绘制前准备
这里根据效果图表盘一共被分成12个区间,一共13个点,在坐标上,为了计算方便,本文中设置每两个相邻的点之间的度数为20°(圆形是360°),每两个小刻度之间的角度为2°,这样整个表盘的其实角度就是-30°,终点角度为210°,表盘扇形区域总度数为240°。
再说Qt坐标系,Qt坐标系是向右为x轴,右为正,左为负,向下为y轴,下为正,上为负。所以这样表盘的相对Qt坐标系的起始角度为-120°,终点角度为120°。
五、详细绘制过程
①准备工作,先设置对话框窗口大小,设置窗口背景颜色等,代码如下所示:

//设置窗口大小
setFixedSize(640,480);
//去掉问号
Qt::WindowFlags flags= this->windowFlags();
setWindowFlags(flags&~Qt::WindowContextHelpButtonHint);
//背景设置成黑色
QPalette bgpal=palette();
bgpal.setColor(QPalette::Background,QColor(0,0,0));
setPalette(bgpal);

②重载窗口绘图函数,初始化QPainter对象,移动绘图坐标中心,设置窗口画笔画刷等,代码如下所示:

void Dashboard::paintEvent(QPaintEvent*)
{
    QPainter painter(this);
    int width=this->width();
    int height=this->height();
    int radius=((width>height)?height:width)/2;
    //将画笔移动到中下方
    painter.translate(width>>1,height*0.6);
    //启用反锯齿
    painter.setRenderHint(QPainter::Antialiasing, true);
    //取消画笔
    painter.setPen(Qt::NoPen);
    //设置画刷颜色
    painter.setBrush(QColor(98,246,255));
}

当前设置完成后,界面一片纯黑,效果图如下所示:

在这里插入图片描述
③绘制13个子弹头。这里分成几步一步步实现,描述清楚坐标计算、坐标移动等,后续步骤中也会用到,不再进行描述。
A.首先,组装子弹头的效果路径,代码如下所示:

//组装点的路径图
QPainterPath pointPath;
pointPath.moveTo(-2,-2);
pointPath.lineTo(2,-2);
pointPath.lineTo(2,2);
pointPath.lineTo(0,4);
pointPath.lineTo(-2,2);

在Qt坐标系中绘制这个闭合图形,得到的效果图如下所示:

在这里插入图片描述
这里可能会有疑问,为什么moveTo坐标是负数,而不是0。这里这样设置主要是将坐标原点绘制在被绘制图形的中心,这样图形旋转之后,图形的中点还是在坐标的中心点。
B.然后计算这些子弹头的旋转坐标值,以左下角的子弹头为例。根据Qt坐标系计算,这个子弹头的旋转角度是-120°(别问我怎么知道的,这个旋转一下就知道了)。下一个子弹头,旋转角度是在-120°的基础上加上20°,依次类推,就可以绘制出所有子弹头的被旋转的图像。
这里用到了Qt坐标系的旋转,函数是rotate函数,是QPainter下的函数。使用方法如下代码所示:

painter.rotate(-120);

旋转之后的子弹头效果图如下所示:

在这里插入图片描述
C.这里既然绘制出了第一个,那剩下的12个绘制就很方便了,因为旋转角度是逐渐递增的,递增角度为20°。所以旋转代码如下:

painter.rotate(-120+i*20);
绘制13个子弹头的代码如下所示:
for(int i=0;i<13;++i){
	painter.save();
	//计算并选择绘图对象坐标
	painter.rotate(-120+i*20);
	//绘制路径
	painter.drawPath(pointPath);
	painter.restore();
}

这里用到QPainter函数的save函数和restore函数,这两个函数一般是成对出现的,这里是先将绘图对象保存一下,因为后面需要旋转坐标,绘制完成之后,恢复绘图对象,被旋转的坐标自然就被恢复了,不然就需要将坐标旋转回来。
这一步因为所有子弹头的绘制坐标都在中心点,所以所有子弹头都重叠在一起,如下图所示:

在这里插入图片描述
D.将子弹头分开。当前由于坐标系原点在控件中间,要将子弹头绘制在圆环上(意识里的圆环上),就需要计算每一个子弹头的坐标。计算每一个子弹头的坐标代码如下所示:

for(int i=0;i<13;++i){
	QPointF point(0,0);
	painter.save();
	//计算绘图对象中心点
	point.setX(radius*qCos(((210-i*20)*M_PI)/180));
	point.setY(radius*qSin(((210-i*20)*M_PI)/180));
	painter.restore();
}

其中point就是子弹头绘制的坐标。为了方便绘图,直接将绘图坐标的中心点移动到计算出来的坐标点上,代码如下所示:

painter.translate(point.x(),-point.y());

这里为什么y坐标是负数?因为三角函数是基于常规的坐标系计算的(y轴向上为正),而Qt坐标系刚好相反(y轴向下为正),所以这里需要y坐标转换一下。
E.完整的绘制13个子弹头的代码如下:

void Dashboard::DrawPoint(QPainter& painter,int radius)
{
    //组装点的路径图
    QPainterPath pointPath;
    pointPath.moveTo(-2,-2);
    pointPath.lineTo(2,-2);
    pointPath.lineTo(2,2);
    pointPath.lineTo(0,4);
    pointPath.lineTo(-2,2);
    //绘制13个小点
    for(int i=0;i<13;++i){
        QPointF point(0,0);
        painter.save();
        //计算并移动绘图对象中心点
        point.setX(radius*qCos(((210-i*20)*M_PI)/180));
        point.setY(radius*qSin(((210-i*20)*M_PI)/180));
        //计算并移动绘图对象的中心点
        painter.translate(point.x(),-point.y());
        //计算并选择绘图对象坐标
        painter.rotate(-120+i*20);
        //绘制路径
        painter.drawPath(pointPath);
        painter.restore();
    }
}

效果如下图:

在这里插入图片描述
④绘制表盘数字。坐标旋转移动中心点的方法和③中相同,后续不在详细描述,直接上代码:

void Dashboard::DrawDigital(QPainter& painter,int radius)
{
    //绘制13个小点
    for(int i=0;i<13;++i){
        QPointF point(0,0);
        painter.save();
        //计算并移动绘图对象中心点
        point.setX(radius*qCos(((210-i*20)*M_PI)/180));
        point.setY(radius*qSin(((210-i*20)*M_PI)/180));
        //计算并移动绘图对象的中心点
        painter.translate(point.x(),-point.y());
        //计算并选择绘图对象坐标
        painter.rotate(-120+i*20);
        //绘制路径
        painter.drawText(-15, 0, 30, 20,Qt::AlignCenter,QString::number(i*20));
        painter.restore();
    }
}

效果图如下:

在这里插入图片描述
很惊奇的发现居然什么都没有?对,确实什么都没有,原因是绘制文字,需要设置画笔,在初始化的时候,将画笔去掉了,这里需要设置画笔,代码如下所示:

painter.setPen(QColor(98,246,255));

效果图如下所示:

在这里插入图片描述
界面看上去比较丑陋,那是因为这里字体字号等都是系统默认的,需要自己设置字体,本工程中字体设置为黑体,字号为16,代码如下所示:

QFont font;
font.setFamily("SimHei");
font.setPointSize(16);
painter.setFont(font);

效果图如下所示:

在这里插入图片描述
⑤绘制渐变圆环。这里采用的饭饭是先计算扇形圆弧的扇形区域,再计算内圆(圆环内测圆)的路径,两个路径求差集(路劲差集就是圆环的路径),绘制锥形渐变,外测为黑色,内测为蓝白色,代码如下所示:

void Dashboard::DrawCircle(QPainter& painter,int radius)
{
    //保存绘图对象
    painter.save();
    //计算大小圆路径
    QPainterPath outRing;
    QPainterPath inRing;
    outRing.moveTo(0,0);
    inRing.moveTo(0,0);
    outRing.arcTo(-radius,-radius, 2*radius,2*radius,-31,242);
    inRing.addEllipse(-radius+20,-radius+20,2*(radius-20),2*(radius-20));
    outRing.closeSubpath();
    //设置渐变色
    QRadialGradient radialGradient(0,0,radius,0,0);
    radialGradient.setColorAt(0.95,QColor(98,246,255));
    radialGradient.setColorAt(1,QColor(0,0,0));
    //设置渐变画刷
    painter.setBrush(radialGradient);
    //大圆减去小圆得到圆环
    painter.drawPath(outRing.subtracted(inRing));
    //恢复绘图对象
    painter.restore();
}

这里有一个问题需要说明,outRing.arcTo函数中,扇形圆的其实角度应该是-31,结束角度应该是240,但是这里偏移了一点点,是因为后续绘制刻度尺的时候,刻度尺的中心点坐标对应的是-30°和240°,刻度尺有宽度,所以看上去就没有对齐,为了让界面看上去对齐,这里扇形圆做了一定的偏移。
效果图如下所示:

在这里插入图片描述
⑥绘制小刻度尺。这里的每个小刻度的绘制方法和上面子弹头的绘制方法一样,唯一不一样的是小刻度的底部不是平的,有一点点突出,为了实现这个效果,这里处理方法是两个梯形合并在一起。路径代码和绘制代码如下所示:

void Dashboard::DrawSmallScale(QPainter& painter,int radius)
{
    //组装点的路径图
    QPainterPath pointPath;
    pointPath.moveTo(-2,-2);
    pointPath.lineTo(-1,-4);
    pointPath.lineTo(1,-4);
    pointPath.lineTo(2,-2);
    pointPath.lineTo(1,8);
    pointPath.lineTo(-1,8);
    //绘制121个小点
    for(int i=0;i<121;++i){
        QPointF point(0,0);
        painter.save();
        //计算并移动绘图对象中心点
        point.setX(radius*qCos(((210-i*2)*M_PI)/180));
        point.setY(radius*qSin(((210-i*2)*M_PI)/180));
        //计算并移动绘图对象的中心点
        painter.translate(point.x(),-point.y());
        //计算并选择绘图对象坐标
        painter.rotate(-120+i*2);
        //绘制路径
        painter.drawPath(pointPath);
        painter.restore();
    }
}

效果图如下所示:

在这里插入图片描述
⑦绘制大刻度。方法和绘制小刻度一样,只是长短刻度需要区分一下。代码如下所示:

void Dashboard::DrawBigScale(QPainter& painter,int radius)
{
    //组装点的路径图
    QPainterPath pointPath1;
    pointPath1.moveTo(-2,-2);
    pointPath1.lineTo(-1,-4);
    pointPath1.lineTo(1,-4);
    pointPath1.lineTo(2,-2);
    pointPath1.lineTo(1,8);
    pointPath1.lineTo(-1,8);
    QPainterPath pointPath2;
    pointPath2.moveTo(-2,-2);
    pointPath2.lineTo(-1,-4);
    pointPath2.lineTo(1,-4);
    pointPath2.lineTo(2,-2);
    pointPath2.lineTo(1,15);
    pointPath2.lineTo(-1,15);
    //绘制25个刻度
    for(int i=0;i<25;++i){
        QPointF point(0,0);
        painter.save();
        //计算并移动绘图对象中心点
        point.setX(radius*qCos(((210-i*10)*M_PI)/180));
        point.setY(radius*qSin(((210-i*10)*M_PI)/180));
        //计算并移动绘图对象的中心点
        painter.translate(point.x(),-point.y());
        //计算并选择绘图对象坐标
        painter.rotate(-120+i*10);
        //绘制路径
        if(i%2){
            painter.drawPath(pointPath1);
        }
        else{
            painter.drawPath(pointPath2);
        }
        painter.restore();
    }
}

效果图如下所示:

在这里插入图片描述
⑧绘制表盘上的文字。这里又需要将画笔设置出来。代码如下所示:

void Dashboard::DrawText(QPainter& painter,int radius)
{
    painter.save();
    //设置画笔
    painter.setPen(QColor(98,246,255));
    //设置字体
    QFont font;
    font.setFamily("Microsoft YaHei");
    font.setPointSize(16);
    painter.setFont(font);
    painter.drawText(-25, -radius, 50, 20,Qt::AlignCenter,QString("km/h"));
    painter.restore();
}

效果图如下所示:

在这里插入图片描述
⑨最后一步是绘制一个指针。废话不多说,直接上代码。如果前面的代码都搞明白了,最后这个指针也就没啥难度了,最可能出问题就是指针底部的半圆路径绘制。

void Dashboard::DrawPointer(QPainter& painter,int radius)
{
    //组装点的路径图
    QPainterPath pointPath;
    pointPath.moveTo(10,0);
    pointPath.lineTo(1,-radius);
    pointPath.lineTo(-1,-radius);
    pointPath.lineTo(-10,0);
    pointPath.arcTo(-10,0,20,20,180,180);
    QPainterPath inRing;
    inRing.addEllipse(-5,-5,10,10);
    painter.save();
    //计算并选择绘图对象坐标
    painter.rotate(-120);
    //设置画刷
    painter.setBrush(QColor(255,0,0,200));
    //绘制路径
    painter.drawPath(pointPath.subtracted(inRing));
    painter.restore();
}

这里指针加了一点点透明度,看上去有点点红黑的感觉,效果图如下所示:

在这里插入图片描述
六、最后总结
①这里只是绘制了一个静态表盘,如果要让指针动起来,修改指针的旋转角度即可。如果要有动画过度效果,那就得加定时器,定时调用绘制代码,让指针一点点的旋转过去,这里没有实现这个功能。
②上面描述的是绘制的整个过程,源码中,设置开始角度、结束角度、颜色,渐变颜色等一系列参数设置函数,可以根据自身需要设置不同的值,实现不同的效果。

七、代码获取
从Git下载,地址为:https://github.com/youyicc/Dashboard1.git

  • 12
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值