简体   繁体   中英

pyside/pyqt how to animate an arc simply?

I'm looking for a solution, to animate this arc from 0 - 360°. I'm relative new to Pyside/Pyqt and I don't find such a simple solution (only beginner "unfriedly"). I tried it with while loops aswell, but it doesn't works. At the moment I don't understand this animation system, but I want to work on it.

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()

Thanks @musicamante for the solution regarding animating arc using QAnimationProperty

Modified @musicmante code to create a loading effect guess it will help developers and might save their time who are trying to make loading effect using Qt

Source

#!/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()

output:

$ ./arc_widget.py 

载入.gif

QPropertyAnimation is used to animate Qt properties of any QObject. If you refer to self (the current instance of QMainWindow), then you can animate all properties of a QMainWindow and all inherited properties (QMainWindow inherits from QWidget, so you can animate all the QWidget properties as well).
In your case, you're trying to animate the width property of the window, and that's certainly not what you want to do.

Since what you want to change is a value that is not a property of the window, you cannot use QPropertyAnimation (unless you create a Qt property using the @Property decorator), and you should use a QVariantAnimation instead.

Then, a paintEvent is called by Qt every time the widget is going to be drawn (which can happen very often), so you cannot create the animation there, otherwise you could end up with a recursion: since the animation would require a repaint, you would create a new animation everytime the previous requires an update.

Finally, consider that painting on a QMainWindow is normally discouraged, as a Qt main window is a special kind of QWidget intended for advanced features (menus, status bar, etc) and uses a central widget to show the actual contents.
The correct approach is to create and set a central widget, and implement the painting on that widget instead.

Here is a revised and working version of your code:

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)

The valueChanged connection ensures that everytime the value changes the widget schedules an update (thus calling a paintEvent as soon as the event queue allows it), then you can use the current value of the animation to draw the actual arc.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM