效果预览
代码实现
from PyQt5.QtCore import QSize, pyqtProperty, QTimer, Qt, QThread, pyqtSignal
from PyQt5.QtGui import QColor, QPainter
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QPushButton, QVBoxLayout, QLabel, QGridLayout
class CircleProgressBar(QWidget):
Color = QColor(37, 162, 208) # 圆圈颜色
Clockwise = True # 顺时针还是逆时针
Delta = 36
def __init__(self, *args, color=None, clockwise=True, **kwargs):
super(CircleProgressBar, self).__init__(*args, **kwargs)
self.angle = 0
self.Clockwise = clockwise
if color:
self.Color = color
self._timer = QTimer(self, timeout=self.update)
def start(self):
"""启动进度条的加载动画"""
self._timer.start(100) # 开始定时器
def stop(self):
"""停止进度条的加载动画并删除控件"""
self._timer.stop()
self.deleteLater() # 删除控件并释放资源
def paintEvent(self, event):
super(CircleProgressBar, self).paintEvent(event)
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.translate(self.width() / 2, self.height() / 2)
side = min(self.width(), self.height())
painter.scale(side / 100.0, side / 100.0)
painter.rotate(self.angle)
painter.save()
painter.setPen(Qt.NoPen)
color = self.Color.toRgb()
for i in range(11):
color.setAlphaF(1.0 * i / 10)
painter.setBrush(color)
painter.drawEllipse(30, -10, 20, 20)
painter.rotate(36)
painter.restore()
self.angle += self.Delta if self.Clockwise else -self.Delta
self.angle %= 360
@pyqtProperty(QColor)
def color(self) -> QColor:
return self.Color
@color.setter
def color(self, color: QColor):
if self.Color != color:
self.Color = color
self.update()
@pyqtProperty(bool)
def clockwise(self) -> bool:
return self.Clockwise
@clockwise.setter
def clockwise(self, clockwise: bool):
if self.Clockwise != clockwise:
self.Clockwise = clockwise
self.update()
@pyqtProperty(int)
def delta(self) -> int:
return self.Delta
@delta.setter
def delta(self, delta: int):
if self.delta != delta:
self.delta = delta
self.update()
def sizeHint(self) -> QSize:
return QSize(100, 100)
class LoadingThread(QThread):
"""模拟加载过程的线程"""
finished = pyqtSignal()
def run(self):
# 在这里模拟加载过程,可以替换为实际的加载逻辑
import time
time.sleep(5) # 模拟加载时间
self.finished.emit() # 加载完成后发出信号
class CustomWidget(QWidget):
"""每个子控件,包含两个按钮"""
def __init__(self, main_window, bg_color, *args, **kwargs):
super(CustomWidget, self).__init__(*args, **kwargs)
self.main_window = main_window
layout = QVBoxLayout(self)
# Set the background color for this widget
self.setStyleSheet(f"background-color: {bg_color};")
# Button to trigger loading animation in the main UI
self.centerLoadButton = QPushButton("主UI中居中加载")
self.centerLoadButton.clicked.connect(self.startLoadingInMain)
layout.addWidget(self.centerLoadButton)
# Button to trigger loading animation in this widget
self.localLoadButton = QPushButton("本控件中加载")
self.localLoadButton.clicked.connect(self.startLoadingInLocal)
layout.addWidget(self.localLoadButton)
def startLoadingInMain(self):
"""在主窗口的中心显示加载动画"""
self.main_window.startLoadingInCenter()
def startLoadingInLocal(self):
"""在本控件内部显示加载动画"""
self.main_window.startLoadingInWidget(self)
class Window(QWidget):
def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs)
self.initUI()
def initUI(self):
layout = QGridLayout(self)
self.setLayout(layout)
# 创建四个子控件,并为每个控件指定不同的背景颜色
widget1 = CustomWidget(self, "lightblue")
widget2 = CustomWidget(self, "lightgreen")
widget3 = CustomWidget(self, "lightcoral")
widget4 = CustomWidget(self, "lightyellow")
layout.addWidget(widget1, 0, 0)
layout.addWidget(widget2, 0, 1)
layout.addWidget(widget3, 1, 0)
layout.addWidget(widget4, 1, 1)
def startLoadingInCenter(self):
"""在主窗口中心显示加载动画"""
self.progressBar = CircleProgressBar(self) # 动态创建进度条
self.progressBar.setFixedSize(100, 100)
self.progressBar.show()
# 设置进度条在主窗口中心
self.progressBar.move(
self.width() // 2 - self.progressBar.width() // 2,
self.height() // 2 - self.progressBar.height() // 2
)
self.progressBar.start() # 手动启动进度条动画
# 启动加载线程
self.loadingThread = LoadingThread()
self.loadingThread.finished.connect(self.progressBar.stop)
self.loadingThread.start()
def startLoadingInWidget(self, widget):
"""在指定的小部件内显示加载动画"""
self.progressBar = CircleProgressBar(widget) # 在小部件中创建进度条
self.progressBar.setFixedSize(50, 50)
self.progressBar.show()
# 设置进度条在小部件中心
self.progressBar.move(
widget.width() // 2 - self.progressBar.width() // 2,
widget.height() // 2 - self.progressBar.height() // 2
)
self.progressBar.start() # 手动启动进度条动画
# 启动加载线程
self.loadingThread = LoadingThread()
self.loadingThread.finished.connect(self.progressBar.stop)
self.loadingThread.start()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
具体介绍
1. 导入必要模块
from PyQt5.QtCore import QSize, pyqtProperty, QTimer, Qt, QThread, pyqtSignal
from PyQt5.QtGui import QColor, QPainter
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QPushButton, QVBoxLayout, QLabel, QGridLayout
- QSize: 控件的尺寸类。
- pyqtProperty: 用于创建自定义的属性,使其可以与PyQt的信号和槽系统协作。
- QTimer: 用于控制加载动画的定时器。
- Qt: 包含常用的Qt标志和属性(例如布局方向、对齐方式等)。
- QThread 和 pyqtSignal: 用于多线程处理,
QThread
允许后台执行任务,pyqtSignal
用于在任务完成时发出信号。 - QColor 和 QPainter: 用于绘制和处理颜色的类。
- QWidget 及其子类用于创建UI控件(例如按钮、布局、标签等)。
2. CircleProgressBar 类
class CircleProgressBar(QWidget):
Color = QColor(37, 162, 208) # 圆圈颜色
Clockwise = True # 是否顺时针旋转
Delta = 36 # 每次旋转的角度增量
- 该类定义了一个自定义的环形进度条,并继承自 QWidget。
- 主要负责绘制并控制环形加载动画。
初始化方法
def __init__(self, *args, color=None, clockwise=True, **kwargs):
super(CircleProgressBar, self).__init__(*args, **kwargs)
self.angle = 0 # 当前旋转的角度
self.Clockwise = clockwise # 控制旋转方向
if color:
self.Color = color # 可以通过参数自定义进度条颜色
self._timer = QTimer(self, timeout=self.update) # 定时器用于定时更新动画
angle
:控制动画的当前角度。QTimer
:每隔100ms触发一次,调用update()
方法刷新绘制。
启动和停止动画的方法
def start(self):
"""启动进度条的加载动画"""
self._timer.start(100)
def stop(self):
"""停止进度条的加载动画并删除控件"""
self._timer.stop()
self.deleteLater() # 删除控件以释放资源
start()
:启动定时器,进而启动动画。stop()
:停止动画并销毁该进度条控件。
绘制环形动画
def paintEvent(self, event):
super(CircleProgressBar, self).paintEvent(event)
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.translate(self.width() / 2, self.height() / 2)
side = min(self.width(), self.height())
painter.scale(side / 100.0, side / 100.0)
painter.rotate(self.angle)
painter.save()
painter.setPen(Qt.NoPen)
color = self.Color.toRgb()
for i in range(11):
color.setAlphaF(1.0 * i / 10)
painter.setBrush(color)
painter.drawEllipse(30, -10, 20, 20)
painter.rotate(36)
painter.restore()
self.angle += self.Delta if self.Clockwise else -self.Delta
self.angle %= 360
paintEvent()
:负责绘制进度条的环形动画。
painter.translate()
:将画布的坐标系中心移动到控件的中心位置。painter.scale()
:缩放画布,以适应控件的大小。painter.rotate()
:旋转环形的每个小圆点。color.setAlphaF()
:为每个圆点设置透明度,透明度从最低到最高,形成渐隐效果。painter.drawEllipse()
:绘制每个小圆点。- 每次更新动画时,角度都会增加或减少,形成旋转效果。
自定义属性
- 使用
pyqtProperty
定义了自定义的属性 color
, clockwise
, delta
,使它们能够与PyQt的属性系统兼容。
3. LoadingThread 类
class LoadingThread(QThread):
"""模拟加载过程的线程"""
finished = pyqtSignal()
def run(self):
import time
time.sleep(5) # 模拟加载时间
self.finished.emit() # 加载完成后发出信号
- 该类继承自
QThread
,用于模拟后台任务(这里通过 time.sleep(5)
模拟了5秒的加载过程)。 finished
信号用于通知主线程加载完成。- 主要用于模拟耗时任务,同时保持UI的响应性。
4. CustomWidget 类
class CustomWidget(QWidget):
"""每个子控件,包含两个按钮"""
def __init__(self, main_window, bg_color, *args, **kwargs):
super(CustomWidget, self).__init__(*args, **kwargs)
layout = QVBoxLayout(self)
self.setStyleSheet(f"background-color: {bg_color};")
self.centerLoadButton = QPushButton("主UI中居中加载")
self.centerLoadButton.clicked.connect(self.startLoadingInMain)
layout.addWidget(self.centerLoadButton)
self.localLoadButton = QPushButton("本控件中加载")
self.localLoadButton.clicked.connect(self.startLoadingInLocal)
layout.addWidget(self.localLoadButton)
- 这是自定义的控件,每个控件内部包含两个按钮:
centerLoadButton
:触发主窗口的居中加载动画。localLoadButton
:在该控件内显示加载动画。
- 每个控件的背景颜色由参数
bg_color
决定。
5. Window 类
class Window(QWidget):
def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs)
self.initUI()
def initUI(self):
layout = QGridLayout(self)
self.setLayout(layout)
# 创建四个子控件,并为每个控件指定不同的背景颜色
widget1 = CustomWidget(self, "lightblue")
widget2 = CustomWidget(self, "lightgreen")
widget3 = CustomWidget(self, "lightcoral")
widget4 = CustomWidget(self, "lightyellow")
layout.addWidget(widget1, 0, 0)
layout.addWidget(widget2, 0, 1)
layout.addWidget(widget3, 1, 0)
layout.addWidget(widget4, 1, 1)
Window
类是应用的主窗口,继承自 QWidget
。- 它使用了 QGridLayout 布局,将四个不同颜色的
CustomWidget
放置在窗口的不同位置。
在窗口中央显示加载动画
def startLoadingInCenter(self):
"""在主窗口中心显示加载动画"""
self.progressBar = CircleProgressBar(self)
self.progressBar.setFixedSize(100, 100)
self.progressBar.show()
self.progressBar.move(
self.width() // 2 - self.progressBar.width() // 2,
self.height() // 2 - self.progressBar.height() // 2
)
self.progressBar.start()
self.loadingThread = LoadingThread()
self.loadingThread.finished.connect(self.progressBar.stop)
self.loadingThread.start()
- 该方法在主窗口中心显示环形进度条。
- 创建进度条后,启动加载动画,并启动
LoadingThread
模拟加载任务。 - 加载完成后,停止并删除进度条。
在子控件内显示加载动画
def startLoadingInWidget(self, widget):
"""在指定的小部件内显示加载动画"""
self.progressBar = CircleProgressBar(widget)
self.progressBar.setFixedSize(50, 50)
self.progressBar.show()
self.progressBar.move(
widget.width() // 2 - self.progressBar.width() // 2,
widget.height() // 2 - self.progressBar.height() // 2
)
self.progressBar.start()
self.loadingThread = LoadingThread()
self.loadingThread.finished.connect(self.progressBar.stop)
self.loadingThread.start()
6. 主程序执行
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
- 程序的入口,创建
QApplication
实例并启动事件循环。
总结
- 该程序展示了如何创建一个带有环形加载动画的PyQt5应用程序。
CircleProgressBar
负责绘制并控制动画,LoadingThread
模拟后台任务。- 主窗口
Window
包含四个不同颜色的子控件,用户可以选择在主窗口或子控件内显示加载动画。