先上成品图~
拖拉窗口下方滑动条,可令指针旋转指向不同速度值,该值会自动显示在表盘中下方的LCD显示屏上。速度范围为0~100Mbps。相比起滑动条的拖拉,指针的旋转动作会有些许延迟,更接近真实情景中汽车仪表盘的显示情况。
本系列博文将对该项目的三个主要部分:仪表盘绘图,LCD屏幕设置和动画设置 进行详述。
绘图部分的重难点主要包括 1. 坐标轴和坐标刻度的变换, 2. 环和指针的绘制,3. 刻度线刻度值的绘制 。
首先,先回顾一下Qt的坐标系统。
一般来讲,我们常用的几个获取控件尺寸的函数,诸如x(), y(), width(), 以及height()都是以 客户区(Client Area) 的左上角顶点为坐标轴原点来计量的。由于在本例中我们需要在窗口中心位置画图,因此为了方便起见,最好将坐标轴原点平移至客户区的中心点,坐标轴的正负方向保持不变,依然是x向右为正,y向下为正。同时,为保证窗口缩放不影响绘图时图线尺寸,故还要进行坐标刻度的“自适应”。代码如下:
def paintEvent(self, event):
# 坐标轴变换
width = self.width()
height = self.height()
painter = QtGui.QPainter(self) # 初始化painter
painter.translate(width / 2, height / 2) # 坐标轴变换,调用translate()将坐标原点平移至窗口中心
# 坐标刻度自适应
side = min(width, height)
painter.scale(side / 200.0, side / 200.0) # 本项目中将坐标缩小为side/200倍,即画出length=10的直线,其实际长度应为10*(side/200)。
# 启用反锯齿,使画出的曲线更平滑
painter.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing)
...
...
def drawColorPie(self, painter): # 绘制三色环
painter.save() # save()保存当前坐标系
# 设置扇形部分区域
radius = 99 # 半径
painter.setPen(QtCore.Qt.NoPen)
rect = QtCore.QRectF(-radius, -radius, radius * 2, radius * 2) # 扇形所在圆区域
# 计算三色圆环范围角度。green:blue:red = 1:2:1
angleAll = 360.0 - self.startAngle - self.endAngle # self.startAngle = 45, self.endAngle = 45
angleStart = angleAll * 0.25
angleMid = angleAll * 0.5
angleEnd = angleAll * 0.25
# 圆的中心部分填充为透明色,形成环的样式
rg = QtGui.QRadialGradient(0, 0, radius, 0, 0) # 起始圆心坐标,半径,焦点坐标
ratio = 0.9 # 透明:实色 = 0.9 :1
# 绘制绿色环
rg.setColorAt(0, QtCore.Qt.transparent) # 透明色
rg.setColorAt(ratio, QtCore.Qt.transparent)
rg.setColorAt(ratio+0.01, self.pieColorStart)
rg.setColorAt(1, self.pieColorStart)
painter.setBrush(rg)
painter.drawPie(rect, (270 - self.startAngle - angleStart) * 16 , angleStart * 16)
# 绘制蓝色环
rg.setColorAt(0, QtCore.Qt.transparent)
rg.setColorAt(ratio, QtCore.Qt.transparent)
rg.setColorAt(ratio+0.01, self.pieColorMid)
rg.setColorAt(1, self.pieColorMid)
painter.setBrush(rg)
painter.drawPie(rect, (270 - self.startAngle - angleStart - angleMid) * 16 , angleMid * 16)
# 绘制红色环
rg.setColorAt(0, QtCore.Qt.transparent)
rg.setColorAt(ratio, QtCore.Qt.transparent)
rg.setColorAt(ratio + 0.01, self.pieColorEnd)
rg.setColorAt(1, self.pieColorEnd)
painter.setBrush(rg)
painter.drawPie(rect, (270 - self.startAngle - angleStart - angleMid - angleEnd) * 16, angleEnd * 16)
painter.restore() # restore()恢复坐标系
def drawPointerIndicator(self, painter):
painter.save()
# 绘制指针
radius = 68 # 指针长度
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(self.pointerColor)
# (-5, 0), (0, -8), (5, 0)和(0, radius) 四个点绘出指针形状
pts = QtGui.QPolygon()
pts.setPoints(-5, 0, 0, -8, 5, 0, 0, radius)
# 旋转指针,使得指针起始指向为0刻度处
painter.rotate(self.startAngle)
degRotate = (360.0 - self.startAngle - self.endAngle) / (self.maxValue - self.minValue)\
* (self.currentValue - self.minValue)
painter.rotate(degRotate)
painter.drawConvexPolygon(pts)
painter.restore()
刻度线的绘制比较简单,用drawLine() 函数即可。绘制时需注意计算主刻度之间的弧度,然后再画短竖线。同样,刻度线也需要进行rotate() 旋转。其关键代码如下:
painter.rotate(self.startAngle) # self.startAngle = 45,旋转45度
steps = 8 # 8个刻度
angleStep = (360.0 - self.startAngle - self.endAngle) / steps # 刻度角
以及每画完一条刻度线,记得旋转指针
painter.setPen(pen)
painter.drawLine(0, radius - 5, 0, radius)
painter.rotate(angleStep)
绘制刻度值用drawText() 函数。和绘制刻度线相比,绘制刻度值时需给出速度值字符串所在的具体坐标。在此我们用python中的math.sin() 和math.cos() 来计算坐标位置。其关键代码如下:
for i in range(self.scaleMajor + 1): # self.scaleMajor = 8, 8个主刻度
# 正余弦计算
sina = math.sin(startRad - i * deltaRad)
cosa = math.cos(startRad - i * deltaRad)
# 刻度值计算
value = math.ceil((1.0 * i * ((self.maxValue - self.minValue) / self.scaleMajor) + self.minValue)) # math.ceil(x):返回不小于x的最小整数
strValue = str(int(value))
# 字符的宽度和高度
textWidth = self.fontMetrics().width(strValue)
textHeight = self.fontMetrics().height()
# 字符串的起始位置。注意考虑到字符宽度和高度进行微调
x = radius * cosa - textWidth / 2
y = -radius * sina + textHeight / 4
painter.drawText(x-offset, y, strValue + "M")
至此,仪表盘的绘制大致完成。每次调用paintEvent()时,都需要重绘上述所有内容。