193 lines
6.1 KiB
Python
193 lines
6.1 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Created on 2018年9月日
|
|
@author: Irony
|
|
@site: https://pyqt.site , https://github.com/PyQt5
|
|
@email: 892768447@qq.com
|
|
@file: MetroCircleProgress
|
|
@description:
|
|
"""
|
|
|
|
try:
|
|
from PyQt5.QtCore import QSequentialAnimationGroup, QPauseAnimation, QPropertyAnimation, \
|
|
QParallelAnimationGroup, QObject, QSize, Qt, QRectF, pyqtSignal, pyqtProperty
|
|
from PyQt5.QtGui import QPainter, QColor
|
|
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout
|
|
except ImportError:
|
|
from PySide2.QtCore import QSequentialAnimationGroup, QPauseAnimation, QPropertyAnimation, \
|
|
QParallelAnimationGroup, QObject, QSize, Qt, QRectF, Signal as pyqtSignal, Property as pyqtProperty
|
|
from PySide2.QtGui import QPainter, QColor
|
|
from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout
|
|
|
|
|
|
class CircleItem(QObject):
|
|
X = 0 # x坐标
|
|
Opacity = 1 # 透明度0~1
|
|
valueChanged = pyqtSignal()
|
|
|
|
@pyqtProperty(float)
|
|
def x(self) -> float:
|
|
return self.X
|
|
|
|
@x.setter
|
|
def x(self, x: float):
|
|
self.X = x
|
|
self.valueChanged.emit()
|
|
|
|
@pyqtProperty(float)
|
|
def opacity(self) -> float:
|
|
return self.Opacity
|
|
|
|
@opacity.setter
|
|
def opacity(self, opacity: float):
|
|
self.Opacity = opacity
|
|
|
|
|
|
def qBound(miv, cv, mxv):
|
|
return max(min(cv, mxv), miv)
|
|
|
|
|
|
class MetroCircleProgress(QWidget):
|
|
Radius = 5 # 半径
|
|
Color = QColor(24, 189, 155) # 圆圈颜色
|
|
BackgroundColor = QColor(Qt.transparent) # 背景颜色
|
|
|
|
def __init__(self, *args, radius=5, color=QColor(24, 189, 155),
|
|
backgroundColor=QColor(Qt.transparent), **kwargs):
|
|
super(MetroCircleProgress, self).__init__(*args, **kwargs)
|
|
self.Radius = radius
|
|
self.Color = color
|
|
self.BackgroundColor = backgroundColor
|
|
self._items = []
|
|
self._initAnimations()
|
|
|
|
@pyqtProperty(int)
|
|
def radius(self) -> int:
|
|
return self.Radius
|
|
|
|
@radius.setter
|
|
def radius(self, radius: int):
|
|
if self.Radius != radius:
|
|
self.Radius = radius
|
|
self.update()
|
|
|
|
@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(QColor)
|
|
def backgroundColor(self) -> QColor:
|
|
return self.BackgroundColor
|
|
|
|
@backgroundColor.setter
|
|
def backgroundColor(self, backgroundColor: QColor):
|
|
if self.BackgroundColor != backgroundColor:
|
|
self.BackgroundColor = backgroundColor
|
|
self.update()
|
|
|
|
def paintEvent(self, event):
|
|
super(MetroCircleProgress, self).paintEvent(event)
|
|
painter = QPainter(self)
|
|
painter.setRenderHint(QPainter.Antialiasing)
|
|
painter.fillRect(self.rect(), self.BackgroundColor)
|
|
painter.setPen(Qt.NoPen)
|
|
|
|
for item, _ in self._items:
|
|
painter.save()
|
|
color = self.Color.toRgb()
|
|
color.setAlphaF(item.opacity)
|
|
painter.setBrush(color)
|
|
# 5<= radius <=10
|
|
radius = qBound(self.Radius, self.Radius / 200 *
|
|
self.height(), 2 * self.Radius)
|
|
diameter = 2 * radius
|
|
painter.drawRoundedRect(
|
|
QRectF(
|
|
item.x / 100 * self.width() - diameter,
|
|
(self.height() - radius) / 2,
|
|
diameter, diameter
|
|
), radius, radius)
|
|
painter.restore()
|
|
|
|
def _initAnimations(self):
|
|
for index in range(5): # 5个小圆
|
|
item = CircleItem(self)
|
|
item.valueChanged.connect(self.update)
|
|
# 串行动画组
|
|
seqAnimation = QSequentialAnimationGroup(self)
|
|
seqAnimation.setLoopCount(-1)
|
|
self._items.append((item, seqAnimation))
|
|
|
|
# 暂停延迟动画
|
|
seqAnimation.addAnimation(QPauseAnimation(150 * index, self))
|
|
|
|
# 加速,并行动画组1
|
|
parAnimation1 = QParallelAnimationGroup(self)
|
|
# 透明度
|
|
parAnimation1.addAnimation(QPropertyAnimation(
|
|
item, b'opacity', self, duration=400, startValue=0, endValue=1.0))
|
|
# x坐标
|
|
parAnimation1.addAnimation(QPropertyAnimation(
|
|
item, b'x', self, duration=400, startValue=0, endValue=25.0))
|
|
seqAnimation.addAnimation(parAnimation1)
|
|
##
|
|
|
|
# 匀速
|
|
seqAnimation.addAnimation(QPropertyAnimation(
|
|
item, b'x', self, duration=2000, startValue=25.0, endValue=75.0))
|
|
|
|
# 加速,并行动画组2
|
|
parAnimation2 = QParallelAnimationGroup(self)
|
|
# 透明度
|
|
parAnimation2.addAnimation(QPropertyAnimation(
|
|
item, b'opacity', self, duration=400, startValue=1.0, endValue=0))
|
|
# x坐标
|
|
parAnimation2.addAnimation(QPropertyAnimation(
|
|
item, b'x', self, duration=400, startValue=75.0, endValue=100.0))
|
|
seqAnimation.addAnimation(parAnimation2)
|
|
##
|
|
|
|
# 暂停延迟动画
|
|
seqAnimation.addAnimation(
|
|
QPauseAnimation((5 - index - 1) * 150, self))
|
|
|
|
for _, animation in self._items:
|
|
animation.start()
|
|
|
|
def sizeHint(self):
|
|
return QSize(100, self.Radius * 2)
|
|
|
|
|
|
class Window(QWidget):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(Window, self).__init__(*args, **kwargs)
|
|
self.resize(800, 600)
|
|
layout = QVBoxLayout(self, spacing=0)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.addWidget(MetroCircleProgress(self))
|
|
layout.addWidget(MetroCircleProgress(self, radius=10))
|
|
layout.addWidget(MetroCircleProgress(self, styleSheet="""
|
|
qproperty-color: rgb(255, 0, 0);
|
|
"""))
|
|
layout.addWidget(MetroCircleProgress(self, styleSheet="""
|
|
qproperty-color: rgb(0, 0, 255);
|
|
qproperty-backgroundColor: rgba(180, 180, 180, 180);
|
|
"""))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
|
|
app = QApplication(sys.argv)
|
|
w = Window()
|
|
w.show()
|
|
sys.exit(app.exec_())
|