该项目展示了若干样式的圆形水位指示器,包括外圆边框的设计,水位样式设计(水平线or波浪线),弧形进度条的旋转等。该系列样式图外观美观大方,指示清晰。其中波浪线动画的绘制是难点,需特别注意。
先上成品图
该项目重难点:
信号槽的连接应该放在整个窗口类中,主要的几个信号-槽函数初始化代码如下:
# 初始化信号槽
def initSignal(self):
# 两个checkbox
self.clockwiseCkBox.stateChanged.connect(self.setRotateDirection)
self.outerFrameCkBox.stateChanged.connect(self.setOuterFrame)
# 5个radiobutton
self.arcRdBtn.toggled.connect(self.setArc)
self.poolRdBtn.toggled.connect(self.setPool)
self.waveRdBtn.toggled.connect(self.setWave)
self.arcpoolRdBtn.toggled.connect(self.setArcPool)
self.arcwaveRdBtn.toggled.connect(self.setArcWave)
# 两个slider
self.processSlider.valueChanged.connect(self.setProcess)
self.angleSlider.valueChanged.connect(self.setStartAngle)
painterEvent设置坐标轴,并调用绘制各部分元素的函数。
def paintEvent(self, event):
# 1. 坐标变换
width = self.width()
height = self.height()
side = min(width, height)
# 2. 绘制准备工作, 启用反锯齿, 平移坐标轴中心, 等比例缩放
painter = QtGui.QPainter(self)
painter.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing)
painter.translate(width / 2, height / 2)
painter.scale(side / 200.0, side / 200.0)
# 3. 绘制固定元素
self.drawInnerCircle(painter) # 绘制内圆
# 4. 绘制可变元素,即勾选不同选项所应绘制的样式
if self.arcStyle: # 如果画的是arcStyle,则1看方向,2看是否有外边框
if self.clockwise:
self.drawClockwiseArc(painter) # 顺时针
else:
self.drawCounterclockwiseArc(painter) # 逆时针
if self.outerFrame: # 有无边框
self.drawOuterFrame(painter)
elif self.poolStyle: # 如果是poolStyle,则默认有边框
self.drawOuterFrame(painter)
self.drawBigPool(painter)
elif self.waveStyle: # 如果是waveStyle,则默认有边框
self.drawOuterFrame(painter)
self.drawBigWave(painter) # 大圆波浪样式
elif self.arc_poolStyle: # 4种情况,顺时针逆时针,有无圆弧。最后要画pool
if self.clockwise:
self.drawClockwiseArc(painter)
else:
self.drawCounterclockwiseArc(painter)
if self.outerFrame:
self.drawOuterFrame(painter)
self.drawSmallPool(painter) # 小圆水池样式
elif self.arc_waveStyle:
if self.clockwise:
self.drawClockwiseArc(painter)
else:
self.drawCounterclockwiseArc(painter)
if self.outerFrame:
self.drawOuterFrame(painter)
self.drawSmallWave(painter) # 小圆波浪样式
self.drawText(painter)
self.update() # 重绘
关于6个圆的圆心坐标,别忘记了圆和圆之间有间隔。同种颜色上下一组,其横坐标总是相同;不同颜色但同排的一组,其纵坐标总是相同。六个圆的大概位置以及核心代码如下:
# 3. 6个圆的圆心坐标点
self.radius = 40 # 内圆半径
self.radiusL = 44 # 大波浪/水池样式所在大圆的半径
self.spacer = 10 # 两圆之间应间隔2*spacer
self.delta = 4 # 44-40 = 4
self.pp = 8 # 波浪浪高
self.halfPen = 4 # 外边框(圆弧)弧线宽度为2 * halfPen
# 蓝色两内圆圆心坐标
self.upBlueXPoint, self.upBlueYPoint = -3 * self.radius - 2 * self.spacer, -2 * self.radius-self.spacer
self.lowBlueXPoint, self.lowBlueYPoint = self.upBlueXPoint, self.spacer
# 红色两内圆圆心坐标
self.upRedXPoint, self.upRedYPoint = -self.radius, self.upBlueYPoint
self.lowRedXPoint, self.lowRedYPoint = self.upRedXPoint, self.lowBlueYPoint
# 绿色两内圆圆心坐标
self.upGreenXPoint, self.upGreenYPoint = self.radius + 2 * self.spacer, self.upBlueYPoint
self.lowGreenXPoint, self.lowGreenYPoint = self.upGreenXPoint, self.lowBlueYPoint
def drawInnerCircle(self, painter):
painter.save() # 保存当前坐标系
# 画笔设置
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(self.colorInnerCircle)
painter.setOpacity(0.2) # 透明度
painter.drawEllipse(self.upBlueXPoint, self.upBlueYPoint, self.radius * 2, self.radius * 2) # blue upper
painter.drawEllipse(self.upBlueXPoint, self.lowBlueYPoint, self.radius * 2, self.radius * 2) # blue lower
painter.drawEllipse(self.upRedXPoint, self.upBlueYPoint, self.radius * 2, self.radius * 2) # red upper
painter.drawEllipse(self.upRedXPoint, self.lowBlueYPoint, self.radius * 2, self.radius * 2) # red lower
painter.drawEllipse(self.upGreenXPoint, self.upBlueYPoint, self.radius * 2, self.radius * 2) # green upper
painter.drawEllipse(self.upGreenXPoint, self.lowBlueYPoint, self.radius * 2, self.radius * 2) # green lower
painter.restore() # 恢复正常坐标系
核心代码:
def drawText(self, painter):
painter.save()
painter.setPen(self.colorText) # 设置字体颜色
font = QtGui.QFont()
font.setFamily("Microsoft YaHei") # 字体种类
font.setLetterSpacing(QtGui.QFont.AbsoluteSpacing, 0) # 间距
font.setPixelSize(28) # 像素大小设置,注意不是字号大小。
painter.setFont(font)
fontMetrics = QtGui.QFontMetrics(font) # 字体尺寸
upValue = str(self.process) + "%"
lowValue = str(self.process)
upTextW = fontMetrics.width(upValue) # 字符串宽度和高度设置
upTextH = fontMetrics.height()
lowTextW = fontMetrics.width(lowValue)
lowTextH = fontMetrics.height()
painter.drawText(QtCore.QRect(-2 * self.radius - 2 * self.lowBlueYPoint - upTextW/2, -self.radius - self.lowBlueYPoint - upTextH/2,
upTextW, upTextH), 0, upValue) # blue upper 注意设置坐标的时候考虑到字符宽度影响
painter.drawText(QtCore.QRect(-2 * self.radius - 2 * self.lowBlueYPoint - lowTextW/2, self.lowBlueYPoint + self.radius - lowTextH/2,
lowTextW, lowTextH), 0, lowValue) # blue lower
...
...
核心代码如下:
painter.drawChord(QtCore.QRect(self.upBlueXPoint, self.upBlueYPoint, self.radius * 2,
self.radius * 2), (-self.process * 1.8 + 270) * 16, self.process * 3.6 * 16) # blue upper drawChord(x,y, 起始角度,跨越角度)度数要*16.
painter.drawChord(QtCore.QRect(self.upBlueXPoint, self.lowBlueYPoint, self.radius * 2,
self.radius * 2), (-self.process * 1.8 + 270) * 16, self.process * 3.6 * 16) # blue lower
该部分是整个项目的难点,主要步骤包括:
核心代码如下:
# 通过QPainterpath.lineTo连接这些正弦分布的点集
for i in range(2 * self.radiusL+1):
pathUpBlueWave1.lineTo(startUpBlueX + i, startUpBlueY + self.pp / 2 * math.sin(
2 * i / self.radiusL * math.pi + self.startWave + self.startAngle / 360 * 2 * math.pi) - self.pp * self.process / 100)
pathUpBlueWave2.lineTo(startUpBlueX + i, startUpBlueY - self.pp / 2 * math.sin(
2 * i / self.radiusL * math.pi + self.startWave + self.startAngle / 360 * 2 * math.pi) - self.pp * self.process / 100)
笔者是绘制了一个上边沿为波浪线的矩形,然后和外圆进行裁剪操作,得到波浪水位的。核心代码如下:
pathInnerCircle = QtGui.QPainterPath()
pathInnerCircle.addEllipse(QtCore.QRectF(startUpBlueX, -2 * self.radiusL-self.spacer + self.delta,
self.radiusL * 2, self.radiusL * 2)) # 把圆加到路径中
pathUpBlueWater1 = pathInnerCircle.intersected(pathUpBlueWave1) # 裁剪操作
pathUpBlueWater2 = pathInnerCircle.intersected(pathUpBlueWave2)
painter.drawPath(pathUpBlueWater1) # 绘制最终裁剪好的路径
painter.drawPath(pathUpBlueWater2)
在本项目中,笔者用了最简单的方式——while循环来定时。核心代码如下:
# 定时器
count = 0
while count < 30000: # count每到30000,就return。然后painterEvent进入update时再次启用计时器。
count += 1
self.startWave += math.pi/20
return
至此,该项目所有重难点总结完毕~?
PyQt5绝大多数内置的绘图函数,其效果和用法与Qt是相同的。开发者不妨参考Qt5的官方手册,或是PyQt5的官方文档来查看。
PyQt官方文档:QPainter
PyQt官方文档:QPen
PyQt官方文档:QBrush
最后,上海的Qter们,欢迎关注公众号 “Qter欢聚在上海” ,让我们共同学习,共同成长?