Search code examples
pythonanimationpyqtpyqt5qpropertyanimation

Line Animation with QPropertyAnimation


Trying to animate a line growing from nothing to a (0,0) to (200, 200) line with PyQt5 and using QPropertyAnimation. I already read a lot of documentation about Qt and tried several samples, but I just cannot get this to work. This is the code I have now:

from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QWidget, QLabel
from PyQt5.QtGui import QPainter, QPixmap, QPainterPath
from PyQt5.QtCore import QObject, QPointF, QPropertyAnimation, pyqtProperty
from PyQt5.QtCore import QLineF
import sys


class Sample(QWidget):

    l1 = QLineF(QPointF(), QPointF())

    def __init__(self):
        super().__init__()

        self.initView()
        self.initAnimation()

    def initView(self):    
        self.show()

    def initAnimation(self):
        self.anim = QPropertyAnimation(self.l1, b'geometry')
        self.anim.setDuration(7000)
        self.anim.setStartValue(QPointF(0, 0))
        self.anim.setEndValue(QPointF(200, 200))
        self.anim.start()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Sample()
    sys.exit(app.exec_())

Not posting all previous attemps, each one fails with a different error. I got a fade out animation on a widget to work, and a picture following a path, but I can seem to make a simple line drawing work. I was hoping to achieve something like this:

Codepen example

Qt documentation is huge and it seems there are several ways to achieve this, painter and timer, animation, variant animation, but I am not very familiar with C++ and the translation to Python is not always easy. Also samples are not that easy to find.

Am I missing something obvious?

Thanks!


For the record, this is what I achieved so far but as soon as I un-comment the QPropertyAnimation creation, app does not launch. Anyway I was still far from the result in the accepted answer.

from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys

class MyLine(QGraphicsLineItem, QObject):
    def __init__(self):
        super().__init__()

    def _set_start(self, point):
        self.setLine(point.x(), point.y(), self.line().p2().x(), self.line().p2().y())

    def _set_end(self, point):
        self.setLine(self.line().p1().x(), self.line().p1().y(), point.x(), point.y())

    start = pyqtProperty(QPointF, fset=_set_start)
    end = pyqtProperty(QPointF, fset=_set_end)


class Example(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        hbox = QHBoxLayout(self)

        self.button = QPushButton("Start", self)
        self.button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        hbox.addWidget(self.button)

        hbox.addSpacing(40)

        self.line = MyLine()
        self.line.setLine(0, 0, 10, 10)
        scene = QGraphicsScene()
        scene.addItem(self.line)
        view = QGraphicsView(scene)
        hbox.addWidget(view)

        self.anim = QPropertyAnimation(self.line, b"end") # crash without error here
        # self.anim.setDuration(2500)
        # self.anim.setLoopCount(1)
        # self.anim.setStartValue(QPointF(10, 10))
        # self.anim.setEndValue(QPointF(200, 200))
        # self.button.clicked.connect(self.anim.start)

        self.setGeometry(300, 300, 380, 250)
        self.setWindowTitle('Color anim')
        self.show()


if __name__ == "__main__":

    app = QApplication([])
    ex = Example()
    ex.show()
    app.exec_()

Solution

  • You have to use QGraphicsView, QGraphicsScene with QGraphicsLineItem as I show below:

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class LineAnimation(QtCore.QObject):
        def __init__(self, parent=None):
            super(LineAnimation, self).__init__(parent)
            self.m_line = QtCore.QLineF()
            self.m_item = QtWidgets.QGraphicsLineItem()
            self.m_item.setLine(self.m_line)
            self.m_item.setPen(
                QtGui.QPen(
                    QtGui.QColor("salmon"),
                    10,
                    QtCore.Qt.SolidLine,
                    QtCore.Qt.SquareCap,
                    QtCore.Qt.RoundJoin,
                )
            )
    
            self.m_animation = QtCore.QPropertyAnimation(
                self,
                b"p2",
                parent=self,
                startValue=QtCore.QPointF(0, 0),
                endValue=QtCore.QPointF(200, 200),
                duration=5 * 1000,
            )
            self.m_animation.start()
    
        def p1(self):
            return self.m_line.p1()
    
        def setP1(self, p1):
            self.m_line.setP1(p1)
            self.m_item.setLine(self.m_line)
    
        def p2(self):
            return self.m_line.p2()
    
        def setP2(self, p2):
            self.m_line.setP2(p2)
            self.m_item.setLine(self.m_line)
    
        p1 = QtCore.pyqtProperty(QtCore.QPointF, fget=p1, fset=setP1)
        p2 = QtCore.pyqtProperty(QtCore.QPointF, fget=p2, fset=setP2)
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super(MainWindow, self).__init__(parent)
            scene = QtWidgets.QGraphicsScene(self)
            view = QtWidgets.QGraphicsView(
                scene, alignment=QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop
            )
            self.setCentralWidget(view)
    
            line_animation = LineAnimation(self)
            scene.addItem(line_animation.m_item)
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = MainWindow()
        w.resize(640, 480)
        w.show()
        sys.exit(app.exec_())