PyQt 实现魔法导航菜单源码分享
- 一、源码分享
-
- 1、源码分享
- 2、效果展示
- 3、库安装
- 二、实现原理
-
- 1、QPainter 概述
-
- 1.1 主要特性
- 1.2 基本使用模式
- 2、QPainter 构造函数与设备管理
-
- 2.1 构造函数
- 2.2 设备管理方法
- 3、绘图状态管理
-
- 3.1 画笔(QPen)设置
- 3.2 画刷(QBrush)设置
- 3.3 字体(QFont)设置
- 3.4 绘图状态保存与恢复
- 4、基本图形绘制方法
-
- 4.1 点与线
- 4.2 矩形绘制
- 4.3 椭圆与圆形
- 4.4 多边形绘制
- 5、文本绘制方法
-
- 5.1 基本文本绘制
- 5.2 文本测量与布局
- 5.3 文本路径绘制
- 6、图像绘制方法
-
- 6.1 基本图像绘制
- 6.2 图像变换与效果
- 7、路径绘制(QPainterPath)
-
- 7.1 创建与绘制路径
- 7.2 路径操作
- 8、坐标变换与视图
-
- 8.1 基本变换
- 8.2 矩阵变换
- 8.3 视图与窗口
- 9、高级绘制特性
-
- 9.1 渲染提示
- 9.2 合成模式
- 9.3 裁剪区域
- 9.4 不透明度与渐变
- 9.5 性能优化技巧
- 9.6 调试与问题排查
一、源码分享
1、源码分享
import math
import sys
from PyQt6.QtWidgets import QWidget, QApplication, QMainWindow, QVBoxLayout
from PyQt6.QtGui import QPainter, QColor, QPixmap, QFont, QPen
from PyQt6.QtCore import Qt, QTimer, QPointF, QRectF
class BottomNavWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setFixedSize(460, 130)
self.setMouseTracking(True)
# 配色
self.root_bg = QColor("#020414")
self.bar_bg = QColor("#ffffff")
self.bar_radius = 15
self.indicator_inner_color = QColor("#2AFC55")
self.font_color = QColor("#000000")
# 动画参数 和QML一致 200ms
self.anim_duration = 200
self.frame_interval = 8
self.step_total = self.anim_duration // self.frame_interval
# 导航数据
self.nav_data = [
{"icon": "image/11.svg", "text": "记事本"},
{"icon": "image/22.svg", "text": "统计"},
{"icon": "image/33.svg", "text": "导航"},
{"icon": "image/44.svg", "text": "QQ"},
{"icon": "image/55.svg", "text": "微信"},
]
self.count = len(self.nav_data)
# 加载图标
self.icon_pixmaps = []
for item in self.nav_data:
pix = QPixmap(item["icon"])
self.icon_pixmaps.append(pix)
# 默认选中第三个导航
self.index = 2
self.target_index = 2
self.anim_frame = 0
self.is_animating = False
self.bar_side_margin = 20
# 动画定时器
self.anim_timer = QTimer(self)
self.anim_timer.setInterval(self.frame_interval)
self.anim_timer.timeout.connect(self._anim_tick)
self._calc_layout()
def _calc_layout(self):
w = self.width()
h = self.height()
# 底部白色栏高度
self.bar_h = h / 3 * 2
self.bar_y = h – self.bar_h
self.item_w = (w – self.bar_side_margin*2) / self.count
# 绿色大圆尺寸
self.indicator_diameter = self.bar_h * 1.15 – 10
# 图标上浮高度
self.icon_up_offset = h / 3.5
# 文字尺寸
self.text_h = h / 8
# 文字初始位置:控件底部外面,完全看不见
self.text_hidden_y = h
# 文字弹出目标位置:白色栏下方、绿色大圆底部
self.text_show_y = h–20
def _anim_tick(self):
self.anim_frame += 1
if self.anim_frame >= self.step_total:
self.index = self.target_index
self.is_animating = False
self.anim_timer.stop()
self.anim_frame = 0
self.update()
def mousePressEvent(self, event):
x = event.position().x() – self.bar_side_margin
click_idx = int(x // self.item_w)
if 0 <= click_idx < self.count and not self.is_animating:
if self.target_index != click_idx:
self.target_index = click_idx
self.is_animating = True
self.anim_frame = 0
self.anim_timer.start()
super().mousePressEvent(event)
def get_progress(self):
if not self.is_animating:
return 1.0
return self.anim_frame / self.step_total
def lerp(self, a, b, t):
return a + (b – a) * t
def paintEvent(self, event):
painter = QPainter(self)
# 绘图开始第一行统一设置渲染提示
painter.setRenderHints(
QPainter.RenderHint.Antialiasing
| QPainter.RenderHint.TextAntialiasing
| QPainter.RenderHint.SmoothPixmapTransform
)
w = self.width()
h = self.height()
painter.setPen(Qt.PenStyle.NoPen)
# 1. 全局黑色背景
painter.setBrush(self.root_bg)
painter.drawRect(self.rect())
# 2. 底部白色圆角导航栏
bar_rect = QRectF(0, self.bar_y, w, self.bar_h)
painter.setBrush(self.bar_bg)
painter.drawRoundedRect(bar_rect, self.bar_radius, self.bar_radius)
t = self.get_progress()
curr_idx = self.index
target_idx = self.target_index
icon_size = self.item_w * 0.42
# 第一阶段:绘制所有图标
for i in range(self.count):
left_x = i * self.item_w
base_icon_y = self.bar_y + (self.bar_h – icon_size) / 2
up_icon_y = base_icon_y – self.icon_up_offset
# 图标上浮插值
if i == curr_idx and self.is_animating:
draw_icon_y = self.lerp(up_icon_y,base_icon_y , t)
elif i == target_idx and self.is_animating:
draw_icon_y = self.lerp(base_icon_y,up_icon_y , t)
elif i == self.index and not self.is_animating:
draw_icon_y = up_icon_y
else:
draw_icon_y = base_icon_y
icon_rect = QRectF(
left_x + (self.item_w – icon_size) / 2 + self.bar_side_margin,
draw_icon_y,
icon_size,
icon_size
)
painter.drawPixmap(icon_rect, self.icon_pixmaps[i], self.icon_pixmaps[i].rect().toRectF())
# 3. 绘制绿色悬浮大圆指示器
curr_ind_x = curr_idx * self.item_w + (self.item_w – self.indicator_diameter) / 2
target_ind_x = target_idx * self.item_w + (self.item_w – self.indicator_diameter) / 2
ind_x = self.lerp(curr_ind_x, target_ind_x, t)
ind_y = self.bar_y – self.indicator_diameter * 0.35
ind_circle = QRectF(ind_x + self.bar_side_margin, ind_y, self.indicator_diameter, self.indicator_diameter)
painter.setBrush(self.indicator_inner_color)
pen = QPen()
pen.setColor(self.root_bg)
pen.setWidth(10)
painter.setPen(pen)
painter.drawEllipse(ind_circle)
# 大圆内部图标
sel_icon_size = self.indicator_diameter * 0.45
sel_idx = round(self.lerp(curr_idx, target_idx, t))
sel_icon_rect = QRectF(
ind_x + (self.indicator_diameter – sel_icon_size) / 2 + self.bar_side_margin,
ind_y + (self.indicator_diameter – sel_icon_size) / 2,
sel_icon_size,
sel_icon_size
)
painter.drawPixmap(sel_icon_rect, self.icon_pixmaps[sel_idx], self.icon_pixmaps[sel_idx].rect().toRectF())
# ===================== 最后绘制文字(顶层,不会被任何图形遮挡) =====================
font = QFont()
font.setPixelSize(14)
font.setFamily("SimHei")
font.setBold(True)
painter.setFont(font)
painter.setPen(self.font_color) # 文字使用Pen渲染,修复文字不显示
for i in range(self.count):
left_x = i * self.item_w
# 文字Y插值逻辑:未选中停在底部隐藏位置,选中向上滑到可视区域
if i == curr_idx and self.is_animating:
text_y = self.lerp(self.text_show_y, self.text_hidden_y, t)
elif i == target_idx and self.is_animating:
text_y = self.lerp(self.text_hidden_y,self.text_show_y, t)
elif i == self.index and not self.is_animating:
text_y = self.text_show_y
else:
text_y = self.text_hidden_y
text_rect = QRectF(left_x + self.bar_side_margin, text_y, self.item_w, self.text_h)
painter.drawText(text_rect, Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter, self.nav_data[i]["text"])
# 主窗口
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Hello World")
self.resize(1000, 650)
central_widget = QWidget()
self.setCentralWidget(central_widget)
central_widget.setStyleSheet("background-color: #020414;")
layout = QVBoxLayout(central_widget)
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(BottomNavWidget())
if __name__ == "__main__":
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec())
2、效果展示

3、库安装

二、实现原理
主要通过QPainter来绘制。
1、QPainter 概述
QPainter 是 PyQt6 中用于执行所有绘图操作的核心类。它提供了丰富的 API 来绘制各种图形、文本、图像和路径,是自定义控件、图表、游戏和可视化应用的基础。
1.1 主要特性
- 设备无关:可在 QWidget、QPixmap、QImage、QPrinter 等多种设备上绘制
- 坐标系系统:支持世界坐标、窗口坐标和视口坐标的转换
- 绘图状态:保存和恢复绘图状态(画笔、画刷、字体等)
- 抗锯齿:支持高质量的抗锯齿渲染
- 变换操作:支持平移、旋转、缩放、剪切等几何变换
1.2 基本使用模式
from PyQt6.QtGui import QPainter, QPen, QBrush, QColor
from PyQt6.QtWidgets import QWidget
class MyWidget(QWidget):
def paintEvent(self, event):
painter = QPainter(self) # 创建 QPainter 对象
painter.setRenderHint(QPainter.RenderHint.Antialiasing) # 开启抗锯齿
# 绘制操作…
painter.drawRect(10, 10, 100, 50)
# QPainter 会在离开作用域时自动结束绘制
2、QPainter 构造函数与设备管理
2.1 构造函数
# 方法1:先创建后绑定设备
painter = QPainter()
painter.begin(widget) # 开始绘制
# … 绘制操作
painter.end() # 结束绘制
# 方法2:创建时直接绑定设备(推荐)
painter = QPainter(widget)
# 方法3:在 QPixmap 上绘制
pixmap = QPixmap(400, 300)
painter = QPainter(pixmap)
2.2 设备管理方法
| begin(QPaintDevice) | 开始在指定设备上绘制 | bool |
| end() | 结束绘制 | None |
| isActive() | 检查是否正在绘制 | bool |
| device() | 获取当前绘制设备 | QPaintDevice |
| setDevice(QPaintDevice) | 设置绘制设备 | None |
3、绘图状态管理
3.1 画笔(QPen)设置
from PyQt6.QtGui import QPen
from PyQt6.QtCore import Qt
# 创建画笔
pen = QPen(QColor("#FF5733"), 2) # 颜色和宽度
pen.setStyle(Qt.PenStyle.DashLine) # 虚线样式
pen.setCapStyle(Qt.PenCapStyle.RoundCap) # 线帽样式
pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin) # 连接样式
painter.setPen(pen) # 应用画笔
painter.setPen(Qt.GlobalColor.red) # 快捷设置(红色,宽度1)
painter.setPen(Qt.PenStyle.NoPen) # 无边框
3.2 画刷(QBrush)设置
from PyQt6.QtGui import QBrush, QLinearGradient, QRadialGradient
# 纯色画刷
brush = QBrush(QColor("#3498db"))
painter.setBrush(brush)
# 渐变画刷
gradient = QLinearGradient(0, 0, 100, 100)
gradient.setColorAt(0, QColor("#1abc9c"))
gradient.setColorAt(1, QColor("#2ecc71"))
painter.setBrush(QBrush(gradient))
# 纹理画刷
texture_brush = QBrush(QPixmap("texture.png"))
painter.setBrush(texture_brush)
# 样式画刷
painter.setBrush(Qt.BrushStyle.Dense4Pattern) # 密集图案
3.3 字体(QFont)设置
from PyQt6.QtGui import QFont
font = QFont("Arial", 12)
font.setBold(True)
font.setItalic(True)
font.setUnderline(True)
font.setPixelSize(16) # 像素大小
font.setPointSize(10) # 点大小
painter.setFont(font)
3.4 绘图状态保存与恢复
# 保存当前状态
painter.save()
# 修改状态
painter.setPen(QColor("blue"))
painter.setBrush(QColor("yellow"))
painter.setFont(QFont("Times", 14))
# 绘制…
painter.drawRect(50, 50, 100, 100)
# 恢复之前的状态
painter.restore()
# 继续使用原始状态绘制
painter.drawRect(200, 50, 100, 100)
4、基本图形绘制方法
4.1 点与线
# 绘制单个点
painter.drawPoint(10, 10)
painter.drawPoint(QPoint(20, 20))
# 绘制多个点
points = [QPoint(10, 10), QPoint(20, 20), QPoint(30, 30)]
painter.drawPoints(points)
# 绘制直线
painter.drawLine(10, 10, 100, 100)
painter.drawLine(QPoint(10, 10), QPoint(100, 100))
# 绘制多条线
lines = [
QLine(10, 10, 50, 50),
QLine(60, 10, 100, 50),
QLine(110, 10, 150, 50)
]
painter.drawLines(lines)
# 绘制折线
polyline = QPolygonF([QPointF(10, 10), QPointF(50, 50), QPointF(100, 10)])
painter.drawPolyline(polyline)
4.2 矩形绘制
from PyQt6.QtCore import QRect, QRectF
# 绘制矩形(多种方式)
painter.drawRect(10, 10, 100, 50) # x, y, width, height
painter.drawRect(QRect(10, 10, 100, 50))
painter.drawRect(QRectF(10.5, 10.5, 100.0, 50.0))
# 绘制圆角矩形
painter.drawRoundedRect(10, 10, 100, 50, 10, 10) # xRadius, yRadius
painter.drawRoundedRect(QRectF(10, 10, 100, 50), 15, 15)
# 填充矩形(使用画刷)
painter.fillRect(10, 10, 100, 50, QColor("#e74c3c"))
painter.fillRect(QRect(10, 10, 100, 50), QBrush(QColor("#3498db")))
# 清除矩形区域
painter.eraseRect(10, 10, 100, 50)
4.3 椭圆与圆形
# 绘制椭圆
painter.drawEllipse(10, 10, 100, 50) # 外接矩形
painter.drawEllipse(QRect(10, 10, 100, 50))
# 绘制圆形(宽高相等的椭圆)
painter.drawEllipse(QPoint(50, 50), 30, 30) # 中心点,x半径,y半径
painter.drawEllipse(QPointF(50.5, 50.5), 25.0, 25.0)
# 绘制弧线
painter.drawArc(10, 10, 100, 100, 30 * 16, 120 * 16) # 角度以1/16度为单位
# 绘制弦
painter.drawChord(10, 10, 100, 100, 30 * 16, 120 * 16)
# 绘制饼图
painter.drawPie(10, 10, 100, 100, 30 * 16, 120 * 16)
4.4 多边形绘制
from PyQt6.QtCore import QPolygon, QPolygonF
# 创建多边形
polygon = QPolygon()
polygon << QPoint(10, 10) << QPoint(50, 10) << QPoint(50, 50) << QPoint(10, 50)
# 绘制多边形
painter.drawPolygon(polygon)
# 使用 QPolygonF(浮点精度)
polygon_f = QPolygonF()
polygon_f << QPointF(10.5, 10.5) << QPointF(50.5, 10.5) << QPointF(50.5, 50.5)
# 填充多边形
painter.drawPolygon(polygon, Qt.FillRule.OddEvenFill)
# 绘制凸多边形(性能更好)
convex_polygon = QPolygon([QPoint(10, 10), QPoint(50, 10), QPoint(30, 50)])
painter.drawConvexPolygon(convex_polygon)
5、文本绘制方法
5.1 基本文本绘制
# 在指定位置绘制文本
painter.drawText(10, 30, "Hello PyQt6")
# 在指定矩形内绘制文本
rect = QRect(10, 10, 200, 50)
painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, "居中文本")
# 带对齐方式的文本
painter.drawText(rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop, "左上对齐")
# 使用 QPoint
painter.drawText(QPoint(10, 30), "在点位置绘制")
# 绘制富文本(HTML)
painter.drawText(10, 50, "<b>粗体</b> <i>斜体</i> <font color='red'>红色</font>")
5.2 文本测量与布局
# 获取文本宽度
text_width = painter.fontMetrics().horizontalAdvance("Hello World")
# 获取文本高度
text_height = painter.fontMetrics().height()
# 获取文本边界矩形
bounding_rect = painter.fontMetrics().boundingRect("Hello World")
# 文本布局
from PyQt6.QtGui import QTextLayout
text = "这是一个多行文本示例,会自动换行"
layout = QTextLayout(text, painter.font())
layout.beginLayout()
while True:
line = layout.createLine()
if not line.isValid():
break
line.setLineWidth(200) # 行宽
# 绘制每一行…
layout.endLayout()
5.3 文本路径绘制
from PyQt6.QtGui import QPainterPath
# 创建文本路径
path = QPainterPath()
path.addText(10, 50, painter.font(), "路径文本")
# 绘制文本路径
painter.drawPath(path)
# 填充文本路径
painter.fillPath(path, QColor("#9b59b6"))
# 描边文本路径
painter.strokePath(path, QPen(QColor("#34495e"), 2))
6、图像绘制方法
6.1 基本图像绘制
from PyQt6.QtGui import QPixmap, QImage
# 加载图像
pixmap = QPixmap("image.png")
image = QImage("photo.jpg")
# 绘制图像(多种方式)
painter.drawPixmap(10, 10, pixmap) # 在指定位置绘制
painter.drawPixmap(QRect(10, 10, 100, 100), pixmap) # 缩放绘制
painter.drawPixmap(QRect(10, 10, 100, 100), pixmap, pixmap.rect()) # 源矩形和目标矩形
# 绘制 QImage
painter.drawImage(10, 10, image)
painter.drawImage(QRect(10, 10, 200, 150), image)
# 绘制图像的一部分
source_rect = QRect(0, 0, 50, 50) # 源图像区域
target_rect = QRect(10, 10, 100, 100) # 目标区域
painter.drawPixmap(target_rect, pixmap, source_rect)
6.2 图像变换与效果
# 保存状态
painter.save()
# 平移后绘制
painter.translate(50, 50)
painter.drawPixmap(0, 0, pixmap)
# 恢复状态
painter.restore()
# 旋转后绘制
painter.save()
painter.translate(100, 100) # 设置旋转中心
painter.rotate(45) # 旋转45度
painter.drawPixmap(–50, –50, pixmap) # 以中心点绘制
painter.restore()
# 缩放绘制
painter.save()
painter.scale(0.5, 0.5) # 缩小一半
painter.drawPixmap(10, 10, pixmap)
painter.restore()
7、路径绘制(QPainterPath)
7.1 创建与绘制路径
from PyQt6.QtGui import QPainterPath
# 创建路径
path = QPainterPath()
# 移动起点
path.moveTo(10, 10)
# 添加直线
path.lineTo(100, 10)
path.lineTo(100, 100)
path.lineTo(10, 100)
path.closeSubpath() # 闭合路径
# 添加曲线
path.cubicTo(50, 50, 100, 150, 150, 50) # 三次贝塞尔曲线
path.quadTo(100, 100, 150, 50) # 二次贝塞尔曲线
# 添加圆弧
path.arcTo(50, 50, 100, 100, 30, 120) # 角度为度
# 添加椭圆
path.addEllipse(200, 200, 50, 30)
# 添加矩形
path.addRect(10, 10, 100, 50)
# 添加圆角矩形
path.addRoundedRect(10, 10, 100, 50, 10, 10)
# 绘制路径
painter.drawPath(path)
# 填充路径
painter.fillPath(path, QColor("#f1c40f"))
# 描边路径
painter.strokePath(path, QPen(QColor("#2c3e50"), 3))
7.2 路径操作
# 路径合并
path1 = QPainterPath()
path1.addRect(10, 10, 50, 50)
path2 = QPainterPath()
path2.addEllipse(30, 30, 50, 50)
# 并集
union_path = path1.united(path2)
# 交集
intersected_path = path1.intersected(path2)
# 差集
subtracted_path = path1.subtracted(path2)
# 绘制结果
painter.drawPath(union_path)
8、坐标变换与视图
8.1 基本变换
# 平移
painter.translate(100, 50) # 向右100,向下50
# 旋转(以原点为中心)
painter.rotate(45) # 顺时针45度
# 缩放
painter.scale(2.0, 0.5) # X轴放大2倍,Y轴缩小一半
# 剪切
painter.shear(0.2, 0.1) # X剪切0.2,Y剪切0.1
# 重置变换
painter.resetTransform()
8.2 矩阵变换
from PyQt6.QtGui import QTransform
# 创建变换矩阵
transform = QTransform()
transform.translate(100, 50)
transform.rotate(30)
transform.scale(1.5, 1.5)
# 应用矩阵变换
painter.setTransform(transform)
# 获取当前变换矩阵
current_transform = painter.transform()
# 组合变换
transform1 = QTransform().translate(50, 50)
transform2 = QTransform().rotate(45)
combined_transform = transform1 * transform2 # 先平移后旋转
painter.setTransform(combined_transform)
8.3 视图与窗口
# 设置视口(物理坐标)
painter.setViewport(0, 0, 400, 300)
# 设置窗口(逻辑坐标)
painter.setWindow(–200, –150, 400, 300)
# 设置世界矩阵
painter.setWorldTransform(QTransform().rotate(30))
# 启用世界变换
painter.setWorldMatrixEnabled(True)
# 坐标映射
logical_point = painter.transform().map(QPoint(100, 100)) # 物理转逻辑
physical_point = painter.transform().inverted().map(QPoint(100, 100)) # 逻辑转物理
9、高级绘制特性
9.1 渲染提示
# 开启抗锯齿(重要!)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
# 文本抗锯齿
painter.setRenderHint(QPainter.RenderHint.TextAntialiasing)
# 平滑像素图变换
painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
# 高质量图像缩放
painter.setRenderHint(QPainter.RenderHint.LosslessImageRendering)
# 组合多个渲染提示
painter.setRenderHints(
QPainter.RenderHint.Antialiasing |
QPainter.RenderHint.TextAntialiasing |
QPainter.RenderHint.SmoothPixmapTransform
)
# 检查是否启用某个渲染提示
if painter.renderHints() & QPainter.RenderHint.Antialiasing:
print("抗锯齿已启用")
9.2 合成模式
from PyQt6.QtCore import Qt
# 设置合成模式
painter.setCompositionMode(QPainter.CompositionMode.SourceOver) # 默认:源覆盖目标
painter.setCompositionMode(QPainter.CompositionMode.Source) # 只绘制源,忽略目标
painter.setCompositionMode(QPainter.CompositionMode.DestinationOver) # 目标覆盖源
painter.setCompositionMode(QPainter.CompositionMode.Clear) # 清除目标区域
painter.setCompositionMode(QPainter.CompositionMode.Xor) # 异或模式
# 常用合成模式示例
# 1. 叠加模式(类似Photoshop的叠加)
painter.setCompositionMode(QPainter.CompositionMode.Multiply) # 正片叠底
painter.setCompositionMode(QPainter.CompositionMode.Screen) # 滤色
painter.setCompositionMode(QPainter.CompositionMode.Overlay) # 叠加
# 2. 混合模式
painter.setCompositionMode(QPainter.CompositionMode.Darken) # 变暗
painter.setCompositionMode(QPainter.CompositionMode.Lighten) # 变亮
# 恢复默认合成模式
painter.setCompositionMode(QPainter.CompositionMode.SourceOver)
9.3 裁剪区域
from PyQt6.QtCore import QRect, QRegion
# 设置矩形裁剪区域
painter.setClipRect(10, 10, 100, 100)
# 设置路径裁剪区域
path = QPainterPath()
path.addEllipse(50, 50, 80, 80)
painter.setClipPath(path)
# 设置区域裁剪(多个矩形组合)
region = QRegion(QRect(10, 10, 50, 50))
region = region.united(QRegion(QRect(70, 70, 50, 50)))
painter.setClipRegion(region)
# 组合裁剪区域(交集)
painter.setClipRect(0, 0, 200, 200)
painter.setClipRect(50, 50, 100, 100, Qt.ClipOperation.IntersectClip)
# 保存和恢复裁剪状态
painter.save()
painter.setClipRect(10, 10, 80, 80)
# 在此裁剪区域内绘制
painter.restore() # 恢复裁剪状态
# 检查裁剪区域
if painter.hasClipping():
clip_rect = painter.clipBoundingRect()
print(f"裁剪区域: {clip_rect}")
# 禁用裁剪
painter.setClipping(False)
9.4 不透明度与渐变
# 设置全局不透明度
painter.setOpacity(0.5) # 50%不透明度
# 使用透明颜色
transparent_brush = QBrush(QColor(255, 0, 0, 128)) # 半透明红色
painter.setBrush(transparent_brush)
# 复杂渐变
from PyQt6.QtGui import QConicalGradient, QRadialGradient, QLinearGradient
# 锥形渐变
conical_gradient = QConicalGradient(100, 100, 0) # 中心点,起始角度
conical_gradient.setColorAt(0, QColor("#ff0000"))
conical_gradient.setColorAt(0.5, QColor("#00ff00"))
conical_gradient.setColorAt(1, QColor("#0000ff"))
painter.setBrush(QBrush(conical_gradient))
# 径向渐变(中心向外)
radial_gradient = QRadialGradient(150, 150, 50, 150, 150) # 中心,半径,焦点
radial_gradient.setColorAt(0, QColor("#ffffff"))
radial_gradient.setColorAt(1, QColor("#000000"))
painter.setBrush(QBrush(radial_gradient))
# 线性渐变(带多个色标)
linear_gradient = QLinearGradient(0, 0, 200, 0) # 起点,终点
linear_gradient.setColorAt(0, QColor("#ff0000"))
linear_gradient.setColorAt(0.3, QColor("#ffff00"))
linear_gradient.setColorAt(0.7, QColor("#00ff00"))
linear_gradient.setColorAt(1, QColor("#0000ff"))
linear_gradient.setSpread(QGradient.Spread.ReflectSpread) # 反射扩展
painter.setBrush(QBrush(linear_gradient))
9.5 性能优化技巧
# 1. 批量绘制(减少状态切换)
painter.save()
painter.setPen(QPen(Qt.GlobalColor.blue, 2))
painter.setBrush(QBrush(Qt.GlobalColor.yellow))
# 批量绘制多个图形
painter.drawRect(10, 10, 50, 50)
painter.drawRect(70, 10, 50, 50)
painter.drawRect(130, 10, 50, 50)
painter.restore()
# 2. 使用 QPainterPath 预定义路径
path = QPainterPath()
path.moveTo(10, 10)
path.lineTo(100, 10)
path.lineTo(100, 100)
path.lineTo(10, 100)
path.closeSubpath()
# 一次性绘制复杂路径
painter.drawPath(path)
# 3. 避免频繁创建 QPainter 对象
# 错误做法:在循环中反复创建 QPainter
# 正确做法:在 paintEvent 外创建一次,重复使用
# 4. 使用 QPicture 记录和重放绘制命令
from PyQt6.QtGui import QPicture
picture = QPicture()
recorder = QPainter(picture)
recorder.drawRect(10, 10, 100, 50)
recorder.drawEllipse(50, 50, 80, 80)
recorder.end()
# 重放绘制命令
painter.drawPicture(0, 0, picture)
# 5. 禁用不需要的渲染提示
painter.setRenderHint(QPainter.RenderHint.Antialiasing, False) # 关闭抗锯齿提升性能
painter.setRenderHint(QPainter.RenderHint.TextAntialiasing, False)
# 6. 使用 QStaticText 绘制静态文本(性能更好)
from PyQt6.QtGui import QStaticText
static_text = QStaticText("这是一个静态文本")
painter.drawStaticText(10, 10, static_text)
# 7. 合理使用 update() 和 repaint()
# update():异步重绘,合并多个更新请求
# repaint():立即重绘,用于需要即时反馈的场景
9.6 调试与问题排查
# 检查绘制设备
if painter.device():
print(f"绘制设备: {painter.device()}")
else:
print("警告:未绑定绘制设备")
# 检查绘制状态
if painter.isActive():
print("QPainter 正在绘制中")
else:
print("QPainter 未激活,请先调用 begin() 或使用带设备的构造函数")
# 检查变换矩阵
transform = painter.transform()
print(f"当前变换矩阵: {transform.m11()}, {transform.m12()}, {transform.m21()}, {transform.m22()}")
# 保存绘制日志
import logging
logging.basicConfig(level=logging.DEBUG)
def paintEvent(self, event):
painter = QPainter(self)
logging.debug(f"开始绘制,设备: {painter.device()}")
try:
# 绘制操作…
painter.drawRect(10, 10, 100, 50)
logging.debug("绘制完成")
except Exception as e:
logging.error(f"绘制错误: {e}")
finally:
# QPainter 会自动结束
pass
# 常见问题:
# 1. 忘记调用 begin() 或使用错误的设备
# 2. 在 paintEvent 外使用 QPainter
# 3. 未正确处理坐标变换
# 4. 内存泄漏:确保 QPainter 在作用域结束时自动销毁


