pyside/pyqt 如何简单地制作弧形动画?

pyside/pyqt how to animate an arc simply?

我正在寻找一个解决方案,从 0 - 360° 为这个圆弧设置动画。我是 Pyside/Pyqt 的新手,我没有找到如此简单的解决方案(只有初学者“不熟悉”)。我也用 while 循环试过,但它不起作用。目前我不明白这个动画系统,但我想研究它。

import sys

from PySide6 import QtCore
from PySide6.QtWidgets import QApplication, QMainWindow
from PySide6.QtCore import Qt
from PySide6.QtGui import QBrush, QPen, QPainter


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle("AnimateArc")
        self.setGeometry(100, 100, 600, 600)

    def paintEvent(self, event):
        self.anim = QtCore.QPropertyAnimation(self, b"width", duration=1000) #<- is there a documentation for b"width", b"geometry"?
        self.anim.setStartValue(0)
        start = 0
        painter = QPainter(self)
        painter.setPen(QPen(Qt.black, 5, Qt.SolidLine))
        painter.drawArc(100, 100, 400, 400, 90 * 16, start * 16)    # I want to make the change dynamicly

        self.anim.setEndValue(360)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    app.exec()

QPropertyAnimation 用于为任何 QObject 的 Qt 属性 设置动画。如果您引用 self(QMainWindow 的当前实例),那么您也可以为所有 properties of a QMainWindow and all inherited properties (QMainWindow inherits from QWidget, so you can animate all the QWidget properties 设置动画。
在您的情况下,您正在尝试为 window 的 width 属性 制作动画,而这肯定不是您想要做的。

由于您要更改的值不是 window 的 属性,因此您不能使用 QPropertyAnimation(除非您使用 [= 创建 Qt 属性 13=] 装饰器),你应该改用 QVariantAnimation。

然后,Qt 每次 将要绘制小部件时都会调用 paintEvent(这可能经常发生),因此您不能在那里创建动画,否则您可能会以递归结束:由于动画需要重新绘制,因此每次前一个动画需要更新时,您都会创建一个新动画。

最后,考虑到通常不鼓励在 QMainWindow 上绘制,因为 Qt main window 是一种特殊的 QWidget,用于高级功能(菜单、状态栏等)并使用 中央小部件 显示实际内容。
正确的做法是创建并设置一个中央小部件,然后在该小部件上实现绘画。

这是您的代码的修订版和工作版本:

class ArcWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.anim = QtCore.QVariantAnimation(self, duration=1000)
        self.anim.setStartValue(0)
        self.anim.setEndValue(360)
        self.anim.valueChanged.connect(self.update)
        self.anim.start()

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setPen(QPen(Qt.black, 5, Qt.SolidLine))
        painter.drawArc(
            100, 100, 400, 400, 90 * 16, self.anim.currentValue() * 16)


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle("AnimateArc")
        self.setGeometry(100, 100, 600, 600)
        self.arcWidget = ArcWidget()
        self.setCentralWidget(self.arcWidget)

valueChanged 连接确保每次值更改时小部件都会安排更新(因此一旦事件队列允许就调用 paintEvent),然后您可以使用当前值绘制实际弧线的动画。

感谢 @musicamante 关于使用 QAnimationProperty

动画弧的解决方案

已修改 @musicmante 代码以创建加载效果我猜这将有助于开发人员,并可能会节省他们尝试使用 Qt 制作 加载效果 的时间

来源

#!/usr/bin/env python3.10

import sys
import string
import random

from PySide6.QtWidgets import (QMainWindow, QPushButton, QVBoxLayout, 
        QApplication, QWidget)
from PySide6.QtCore import (Qt, QVariantAnimation)
from PySide6.QtGui import (QPen, QPainter, QColor)

class Arc:
    colors = list(string.ascii_lowercase[0:6]+string.digits)

    shades_of_blue = ["#7CB9E8","#00308F","#72A0C1", "#F0F8FF",
            "#007FFF", "#6CB4EE", "#002D62", "#5072A7", 
            "#002244", "#B2FFFF", "#6F00FF", "#7DF9FF","#007791",
            "#ADD8E6", "#E0FFFF", "#005f69", "#76ABDF",
            "#6A5ACD", "#008080", "#1da1f2", "#1a1f71", "#0C2340"]

    shades_of_green = ['#32CD32', '#CAE00D', '#9EFD38', '#568203', '#93C572',
            '#8DB600', '#708238', '#556B2F', '#014421', '#98FB98', '#7CFC00',
            '#4F7942', '#009E60', '#00FF7F', '#00FA9A', '#177245', '#2E8B57', 
            '#3CB371', '#A7F432', '#123524', '#5E8C31', '#90EE90', '#03C03C',
            '#66FF00', '#006600', '#D9E650']

    def __init__(self):
        self.diameter = random.randint(100, 600)

        #cols = list(Arc.colors)
        #random.shuffle(cols)
        #_col = "#"+''.join(cols[:6])
        #print(f"{_col=}")
        #self.color = QColor(_col)

        #self.color = QColor(Arc.shades_of_blue[random.randint(0, len(Arc.shades_of_blue)-1)])
        self.color = QColor(Arc.shades_of_green[random.randint(0, len(Arc.shades_of_green)-1)])
        #print(f"{self.color=}")
        self.span = random.randint(40, 150)
        self.direction = 1 if random.randint(10, 15)%2 == 0 else -1
        self.startAngle = random.randint(40, 200)
        self.step = random.randint(100, 300)

class ArcWidget(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()
        self.arcs = [Arc() for i in range(random.randint(10, 20))]
        self.startAnime()

    def initUI(self):
        #self.setAutoFillBackground(True)
        self.setAttribute(Qt.WA_StyledBackground, True)
        self.setStyleSheet("background-color:black;")

    def startAnime(self):

        self.anim = QVariantAnimation(self, duration = 2000)
        self.anim.setStartValue(0)
        self.anim.setEndValue(360)
        self.anim.valueChanged.connect(self.update)
        self.anim.start()


    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        #painter.setPen(QPen(QColor("#b6faec"), 5, Qt.SolidLine))
        #painter.drawArc(
        #        100, 100, 400, 400, 90*16, 
        #        self.anim.currentValue() * 16)
        #width = 400
        #height = 400
        #painter.drawArc(self.width()/2 -width/2, self.height()/2 - height/2, 400, 400, self.anim.currentValue()*16, 45*16)
        for arc in self.arcs:
            painter.setPen(QPen(arc.color, 6, Qt.SolidLine))
            painter.drawArc(self.width()/2 - arc.diameter/2,
                    self.height()/2 - arc.diameter/2, arc.diameter, 
                    arc.diameter, self.anim.currentValue()*16*arc.direction+arc.startAngle*100, arc.span*16) 
            #print(f"currentValue : {self.anim.currentValue()}")
            #arc.startAngle = random.randint(50, 200)
        if self.anim.currentValue() == 360:
            #print("360")
            self.startAnime()

class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()
        self.setWindowTitle("Animate Arc")
        self.setGeometry(100, 100, 600, 600)
        self.arcWidget = ArcWidget()
        self.setCentralWidget(self.arcWidget)

if __name__ == "__main__":
    app = QApplication(sys.argv)

    mainWindow = MainWindow()
    mainWindow.show()

    app.exec()

输出:

$ ./arc_widget.py