之前在网上看到一个用Qt设计的界面
作者免费提供了dll库以供使用,但是作为学习者,我们需要研究其代码以便了解大佬的开发技巧。当然花钱是不可能花钱的,这辈子都不可能花钱的。
我根据作者提供的核心代码脑补了剩余的部分。
作者源文章:Qt编写自定义控件1-汽车仪表盘
分析根据我的理解,绘制过程如下:
绘制一个黑色实心圆作为底部 绘制第二个黑色实现圆,因为两个圆颜色不一样,形成最外圈的圆环 绘制彩色圆饼图 绘制灰色实心圆将彩色圆饼图裁成圆环 绘制刻度线 绘制刻度数值 绘制数值指示指针 绘制淡红色实心圆 绘制白色实心圆 绘制数值文字 绘制遮罩,形成反光效果 开发首先创建一个空白Qt Widget工程,基类为QWidget,不需要ui文件。
Qt使用QPainter在paintEvent()函数中绘图。
1 2 3 4 5 6 7 8 9 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 / 200.0 , side / 200.0 );
在正式绘图前先进行简单处理。
先获取窗口长、宽中比较小的一个值slide;设置反采样;将painter的坐标系平移到窗口中心;设置画笔缩放因子
这样当窗口大小调整时,图像也会自动调整位置和大小
背景圆先绘制背景圆
1 2 3 4 5 6 7 8 9 void GaugeCar::drawOuterCircle (QPainter *painter) { int radius = 99 ; painter->save (); painter->setPen (Qt::NoPen); painter->setBrush (outerCircleColor); painter->drawEllipse (-radius, -radius, radius * 2 , radius * 2 ); painter->restore (); }
将代码模块化,在paintEvent()中调用这个函数就可以了。
Qt的坐标圆点在窗口左上角,因为我们把painter的坐标系移至窗口中央,所以绘制圆时为-radius,-radius。当然圆的绘制方法是长方形的内接圆,所以绘制圆的后两个参数w/h为radius * 2。
效果为
绘制圆的坐标系为
水平为x轴,向右为正,垂直为y轴,向下为正,所以左上角点(x,y)坐标为(-r,-r)。圆所在长方形的宽高为2r,2r。
颜色就选0x505050
内圆在背景圆内部绘制一个浅色圆形成圆环。
1 2 3 4 5 6 7 8 9 void GaugeCar::drawInnerCircle (QPainter *painter) { int radius = 90 ; painter->save (); painter->setPen (Qt::NoPen); painter->setBrush (innerCircleColor); painter->drawEllipse (-radius, -radius, radius * 2 , radius * 2 ); painter->restore (); }
圆半径要比背景圆小,颜色选0x404040
效果为
彩色饼图1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 void GaugeCar::drawColorPie (QPainter *painter) { int radius = 60 ; painter->save (); painter->setPen (Qt::NoPen); QRectF rect (-radius, -radius, radius * 2 , radius * 2 ) ; if (pieStyle == PieStyle_Three) { double angleAll = 360.0 - startAngle - endAngle; double angleStart = angleAll * 0.7 ; double angleMid = angleAll * 0.15 ; double angleEnd = angleAll * 0.15 ; int offset = 3 ; painter->setBrush (pieColorStart); painter->drawPie (rect, (270 - startAngle - angleStart) * 16 , angleStart * 16 ); painter->setBrush (pieColorMid); painter->drawPie (rect, (270 - startAngle - angleStart - angleMid) * 16 + offset, angleMid * 16 ); painter->setBrush (pieColorEnd); painter->drawPie (rect, (270 - startAngle - angleStart - angleMid - angleEnd) * 16 + offset * 2 , angleEnd * 16 ); } else if (pieStyle == PieStyle_Current) { double angleAll = 360.0 - startAngle - endAngle; double angleCurrent = angleAll * ((currentValue - minValue) / (maxValue - minValue)); double angleOther = angleAll - angleCurrent; painter->setBrush (pieColorStart); painter->drawPie (rect, (270 - startAngle - angleCurrent) * 16 , angleCurrent * 16 ); painter->setBrush (pieColorEnd); painter->drawPie (rect, (270 - startAngle - angleCurrent - angleOther) * 16 , angleOther * 16 ); } painter->restore (); }
效果为
饼图分为两种:两种色、三种色
三种色取值为0x17bb99 0xd8d800 0xfd6969
三色三色分别占比:0.7 0.15 0.15
扇形绘制方法和实心圆类似,不过在长方形内接圆的基础上增加了起始和结束角度。
代码中*16
是Qt的角度固定表示方式。
角度以x轴正方向为0度,逆时针绕一圈回来后为360度。而270度是y轴正方向,也就是说此圆饼的起始结束角度为正值,比如本图所示的起始结束角度都为60度。
两色和三色差不多,只不过颜色少一点。
裁成圆环在圆饼上绘制一个实心圆将饼圆裁成圆弧
1 2 3 4 5 6 7 8 9 void GaugeCar::drawCoverCircle (QPainter *painter) { int radius = 50 ; painter->save (); painter->setPen (Qt::NoPen); painter->setBrush (coverCircleColor); painter->drawEllipse (-radius, -radius, radius * 2 , radius * 2 ); painter->restore (); }
刻度线1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 void GaugeCar::drawScale (QPainter *painter) { int radius = 72 ; painter->save (); painter->rotate (startAngle); int steps = (scaleMajor * scaleMinor); double angleStep = (360.0 - startAngle - endAngle) / steps; QPen pen; pen.setColor (scaleColor); pen.setCapStyle (Qt::RoundCap); for (int i = 0 ; i <= steps; i++) { if (i % scaleMinor == 0 ) { pen.setWidthF (1.5 ); painter->setPen (pen); painter->drawLine (0 , radius - 10 , 0 , radius); } else { pen.setWidthF (0.5 ); painter->setPen (pen); painter->drawLine (0 , radius - 5 , 0 , radius); } painter->rotate (angleStep); } painter->restore (); }
效果为
刻度线分为粗线和细线,每遇到整值就绘制粗线。使用变量控制刻度线的数量,一般每10根细线绘制一根粗线。先决定一根线作为基准,然后使用画笔的rotate选择功能绕坐标原点旋转。
刻度数值1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void GaugeCar::drawScaleNum (QPainter *painter) { int radius = 82 ; painter->save (); painter->setPen (scaleColor); double startRad = (360 - startAngle - 90 ) * (M_PI / 180 ); double deltaRad = (360 - startAngle - endAngle) * (M_PI / 180 ) / scaleMajor; for (int i = 0 ; i <= scaleMajor; i++) { double sina = qSin (startRad - i * deltaRad); double cosa = qCos (startRad - i * deltaRad); double value = 1.0 * i * ((maxValue - minValue) / scaleMajor) + minValue; QString strValue = QString ("%1" ).arg ((double )value, 0 , 'f' , precision); double textWidth = fontMetrics ().horizontalAdvance (strValue); double textHeight = fontMetrics ().height (); int x = radius * cosa - textWidth / 2 ; int y = -radius * sina + textHeight / 4 ; painter->drawText (x, y, strValue); } painter->restore (); }
此处不能用rotate,因为在100处会显示完全倒过来的数字。
使用一个变量控制数值精度,也就是小数点位数。
数值指示指针1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 void GaugeCar::drawPointerCircle (QPainter *painter) { int radius = 6 ; int offset = 30 ; painter->save (); painter->setPen (Qt::NoPen); painter->setBrush (pointerColor); painter->rotate (startAngle); double degRotate = (360.0 - startAngle - endAngle) / (maxValue - minValue) * (currentValue - minValue); painter->rotate (degRotate); painter->drawEllipse (-radius, radius + offset, radius * 2 , radius * 2 ); painter->restore (); }void GaugeCar::drawPointerIndicator (QPainter *painter) { int radius = 75 ; painter->save (); painter->setOpacity (0.8 ); painter->setPen (Qt::NoPen); painter->setBrush (pointerColor); QPolygon pts; pts.setPoints (3 , -5 , 0 , 5 , 0 , 0 , radius); painter->rotate (startAngle); double degRotate = (360.0 - startAngle - endAngle) / (maxValue - minValue) * (currentValue - minValue); painter->rotate (degRotate); painter->drawConvexPolygon (pts); painter->restore (); }void GaugeCar::drawPointerIndicatorR (QPainter *painter) { int radius = 75 ; painter->save (); painter->setOpacity (1.0 ); QPen pen; pen.setWidth (1 ); pen.setColor (pointerColor); painter->setPen (pen); painter->setBrush (pointerColor); QPolygon pts; pts.setPoints (3 , -5 , 0 , 5 , 0 , 0 , radius); painter->rotate (startAngle); double degRotate = (360.0 - startAngle - endAngle) / (maxValue - minValue) * (currentValue - minValue); painter->rotate (degRotate); painter->drawConvexPolygon (pts); pen.setCapStyle (Qt::RoundCap); pen.setWidthF (4 ); painter->setPen (pen); painter->drawLine (0 , 0 , 0 , radius); painter->restore (); }void GaugeCar::drawPointerTriangle (QPainter *painter) { int radius = 10 ; int offset = 38 ; painter->save (); painter->setPen (Qt::NoPen); painter->setBrush (pointerColor); QPolygon pts; pts.setPoints (3 , -5 , 0 + offset, 5 , 0 + offset, 0 , radius + offset); painter->rotate (startAngle); double degRotate = (360.0 - startAngle - endAngle) / (maxValue - minValue) * (currentValue - minValue); painter->rotate (degRotate); painter->drawConvexPolygon (pts); painter->restore (); }
就是绘制不同形状的指示针,然后根据数值计算旋转角度,使用rotate绘图
淡红色实心圆绘制圆就简单了
1 2 3 4 5 6 7 8 9 10 void GaugeCar::drawRoundCircle (QPainter *painter) { int radius = 18 ; painter->save (); painter->setOpacity (0.8 ); painter->setPen (Qt::NoPen); painter->setBrush (pointerColor); painter->drawEllipse (-radius, -radius, radius * 2 , radius * 2 ); painter->restore (); }
颜色和指针一样
中心圆1 2 3 4 5 6 7 8 9 void GaugeCar::drawCenterCircle (QPainter *painter) { int radius = 15 ; painter->save (); painter->setPen (Qt::NoPen); painter->setBrush (centerCircleColor); painter->drawEllipse (-radius, -radius, radius * 2 , radius * 2 ); painter->restore (); }
数值文字1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void GaugeCar::drawValue (QPainter *painter) { int radius = 100 ; painter->save (); painter->setPen (textColor); QFont font; font.setPixelSize (18 ); painter->setFont (font); QRectF textRect (-radius, -radius, radius * 2 , radius * 2 ) ; QString strValue = QString ("%1" ).arg ((double )currentValue, 0 , 'f' , precision); painter->drawText (textRect, Qt::AlignCenter, strValue); painter->restore (); }
反光效果1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 void GaugeCar::drawOverlay (QPainter *painter) { if (!showOverlay) { return ; } int radius = 90 ; painter->save (); painter->setPen (Qt::NoPen); QPainterPath smallCircle; QPainterPath bigCircle; radius -= 1 ; smallCircle.addEllipse (-radius, -radius, radius * 2 , radius * 2 ); radius *= 2 ; bigCircle.addEllipse (-radius, -radius + 140 , radius * 2 , radius * 2 ); QPainterPath highlight = smallCircle - bigCircle; QLinearGradient linearGradient (0 , -radius / 2 , 0 , 0 ) ; overlayColor.setAlpha (100 ); linearGradient.setColorAt (0.0 , overlayColor); overlayColor.setAlpha (30 ); linearGradient.setColorAt (1.0 , overlayColor); painter->setBrush (linearGradient); painter->rotate (-20 ); painter->drawPath (highlight); painter->restore (); }
这里有一个绘制图像的方法,先设计一个大圆,然后设计一个小圆,使用QPainterPath中的-
或者QRegion的subtracted方法,把小圆对应的部分从大圆中减掉,就获得了一个圆环。当然还有并、取反、异或操作。
效果为
控件上面的部分只是绘制仪表盘,如果需要把它变成独立的控件还需要把一些接口添加上。
设置颜色的接口、设置数值的接口等等,同时还有获取属性的接口。
将这些接口API添加完毕后只能称为独立的模块。我们想要的是在Designer中拖拽使用。
新建一个自定义控件工程
设置工程名称
设置属性
然后保持默认一路点下去。
然后将我们开发好的类直接替换控件的文件
再添加个图片作为控件的图标
编译得到一个动态链接库文件
然后将此动态链接库复制到Designer插件目录,Manjaro Linux命令
1 sudo cp libgaugecarplugin.so /usr/lib/qt/plugins/designer
再次打开Designer
这样就开发出了自己的控件。