QPropertyAnimation in PyQt

QPropertyAnimation in PyQt shows how to create animations in PyQt with QPropertyAnimation. In the examples we animation size, colour, and position of objects. The sources and the ball image can be found at the author's Github repository.

QPropertyAnimation

QPropertyAnimation interpolates over PyQt properties. A class declaring properties must be a QObject.

QPropertyAnimation methods

The following table shows a few important QPropertyAnimation methods:

NameDescription
start()starts animation
stop()terminates animation
setStartValue()sets the starting value of animation
setEndValue()sets the ending value of animation
setDuration()sets the duration of animation, in ms
setKeyValueAt()creates a key frame at the given step with the given value
setLoopCount()sets the number of repetitions of the animation

Animating size with QPropertyAnimation

In the first example we animate the size of a widget.

size_anim.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-

'''
ZetCode Advanced PyQt5 tutorial 

This program animates the size of a
widget with QPropertyAnimation.

Author: Jan Bodnar
Website: zetcode.com 
Last edited: August 2017
'''

from PyQt5.QtWidgets import QWidget, QApplication, QFrame, QPushButton
from PyQt5.QtCore import QRect, QPropertyAnimation
import sys
             
             
class Example(QWidget):

    def __init__(self):
        super().__init__()
        
        
        self.initUI()
        
    def initUI(self):
        
        self.button = QPushButton("Start", self)
        self.button.clicked.connect(self.doAnim)
        self.button.move(30, 30)
        
        self.frame = QFrame(self)
        self.frame.setFrameStyle(QFrame.Panel | QFrame.Raised)
        self.frame.setGeometry(150, 30, 100, 100)
        
        self.setGeometry(300, 300, 380, 300)
        self.setWindowTitle('Animation')
        self.show()        
        

    def doAnim(self):

        self.anim = QPropertyAnimation(self.frame, b"geometry")
        self.anim.setDuration(10000)
        self.anim.setStartValue(QRect(150, 30, 100, 100))
        self.anim.setEndValue(QRect(150, 30, 200, 200))
        self.anim.start()

if __name__ == "__main__":
    
    app = QApplication([])
    ex = Example()
    ex.show()
    app.exec_()

The example animates the size of a QFrame widget.

self.button = QPushButton("Start", self)
self.button.clicked.connect(self.doAnim)
self.button.move(30, 30)

The animation is started with a QPushButton.

self.anim = QPropertyAnimation(self.frame, b"geometry")

QPropertyAnimation is created. The first parameter is the target object to be animation; in our case, we animate a QFrame widget. The second parameter is the property that is going to be changed.

self.anim.setDuration(10000)

The setDuration() sets the duration of the animation in milliseconds.

self.anim.setStartValue(QRect(150, 30, 100, 100))
self.anim.setEndValue(QRect(150, 30, 200, 200))

With setStartValue() and setEndValue(), we define the starting and ending values of the animation, respectively.

self.anim.start()

Animation begins with the start() method.

Animating colour with QPropertyAnimation

The following example animates the colour of a widget. Since there is no colour property, we have to create one.

color_anim.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-

'''
ZetCode Advanced PyQt5 tutorial 

This programs animates the color of a QLabel.

Author: Jan Bodnar
Website: zetcode.com 
Last edited: August 2017
'''

from PyQt5.QtWidgets import (QWidget, QApplication, QPushButton, 
        QLabel, QHBoxLayout, QSizePolicy)
from PyQt5.QtGui import QColor
from PyQt5.QtCore import QPropertyAnimation, pyqtProperty
import sys


class MyLabel(QLabel):
    
    def __init__(self, text):
        super().__init__(text)

    def _set_color(self, col):
        
        palette = self.palette()
        palette.setColor(self.foregroundRole(), col)
        self.setPalette(palette)

    color = pyqtProperty(QColor, fset=_set_color)


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.label = MyLabel("Summer")
        font = self.label.font()
        font.setPointSize(35)
        self.label.setFont(font)
        hbox.addWidget(self.label)

        self.anim = QPropertyAnimation(self.label, b"color")
        self.anim.setDuration(2500)
        self.anim.setLoopCount(2)
        self.anim.setStartValue(QColor(0, 0, 0))
        self.anim.setEndValue(QColor(255, 255, 255))

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

The example gradually changes the colour value of a QLabel.

class MyLabel(QLabel):
    
    def __init__(self, text):
        super().__init__(text)

    def _set_color(self, col):
        
        palette = self.palette()
        palette.setColor(self.foregroundRole(), col)
        self.setPalette(palette)

    color = pyqtProperty(QColor, fset=_set_color)

QLabel does not have a color property; therefore, we define one with pyqtProperty. Changing this property updates the colour of the label.

self.anim = QPropertyAnimation(self.label, b"color")

The QPropertyAnimation changes the color property of the label widget.

self.anim.setLoopCount(2)

With the setLoopCount() method we can change how many times the animation will run.

self.anim.setStartValue(QColor(0, 0, 0))
self.anim.setEndValue(QColor(255, 255, 255))

We set the starting and ending colour values.

Animation along curve with QPropertyAnimation

The following example animates a ball along a Bézier curve.

anim_along_curve.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-

'''
ZetCode Advanced PyQt5 tutorial

This programs animates a ball object 
along a curve.

Author: Jan Bodnar
Website: zetcode.com
Last edited: August 2017
'''

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

                              
class Ball(QLabel):
    
    def __init__(self, parent):
        super().__init__(parent)
        
        pix = QPixmap("ball.png")
        self.h = pix.height()
        self.w = pix.width()
        
        self.setPixmap(pix)
        
    def _set_pos(self, pos):
        
        self.move(pos.x() - self.w/2, pos.y() - self.h/2)

    pos = pyqtProperty(QPointF, fset=_set_pos)   
       
    
class Example(QWidget):
    
    def __init__(self):
        super().__init__()

        self.initView()
        self.initAnimation()
        
        
    def initView(self):    
        
        self.path = QPainterPath()
        self.path.moveTo(30, 30)
        self.path.cubicTo(30, 30, 200, 350, 350, 30)        
        
        self.ball = Ball(self)

        self.ball.pos = QPointF(30, 30)
        
        self.setWindowTitle("Animation along curve")
        self.setGeometry(300, 300, 400, 300)
        self.show()
        
        
    def paintEvent(self, e):    
        
        qp = QPainter()
        qp.begin(self)
        qp.setRenderHint(QPainter.Antialiasing)   
        qp.drawPath(self.path)
        qp.end()             

        
    def initAnimation(self):
        
        self.anim = QPropertyAnimation(self.ball, b'pos')
        self.anim.setDuration(7000)
        
        self.anim.setStartValue(QPointF(30, 30))
        
        vals = [p/100 for p in range(0, 101)]

        for i in vals:
            self.anim.setKeyValueAt(i, self.path.pointAtPercent(i))  
                
        self.anim.setEndValue(QPointF(350, 30))        
        self.anim.start()
        
                  
if __name__ == '__main__':
    
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

The example draws a curve on the window. It animates a ball object along the painted curve.

class Ball(QLabel):
    
    def __init__(self, parent):
        super().__init__(parent)
        
        pix = QPixmap("ball.png")
        self.h = pix.height()
        self.w = pix.width()
        
        self.setPixmap(pix)

The ball is displayed in a QLabel widget.

def _set_pos(self, pos):
    
    self.move(pos.x() - self.w/2, pos.y() - self.h/2)

pos = pyqtProperty(QPointF, fset=_set_pos)   

We adjust the position of the ball; we want the middle of the label to be placed on the curve.

self.path = QPainterPath()
self.path.moveTo(30, 30)
self.path.cubicTo(30, 30, 200, 350, 350, 30) 

The Bézier curve is created with QPainterPath. Its cubicTo() method takes starting point, control point, and ending point as parameters.

def paintEvent(self, e):    
    
    qp = QPainter()
    qp.begin(self)
    qp.setRenderHint(QPainter.Antialiasing)   
    qp.drawPath(self.path)
    qp.end() 

The curve is painted with the drawPath() method in the paintEvent() method.

self.anim = QPropertyAnimation(self.ball, b'pos')

We animate the ball's pos property with QPropertyAnimation.

vals = [p/100 for p in range(0, 101)]

With the Python list comprehension, we create a list of animation steps. The steps are values between 0 and 1.

for i in vals:
    self.anim.setKeyValueAt(i, self.path.pointAtPercent(i))  

With setKeyValueAt(), we define the position of the ball at the given step. With pointAtPercent(), we get the QPointF at the given percentage of the path.

Animation along curve
Figure: Animation along curve

QPropertyAnimation in Graphics View Framework

QPropertyAnimation can animate graphics items in the Graphics View Framework. The animated object must inherit from QObject and QGraphicsItem.

gvf_anim.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-

'''
ZetCode Advanced PyQt5 tutorial

This programs animates a ball object. 

Author: Jan Bodnar
Website: zetcode.com
Last edited: August 2017
'''

from PyQt5.QtWidgets import (QApplication, QGraphicsView, 
        QGraphicsPixmapItem, QGraphicsScene)
from PyQt5.QtGui import QPainter, QPixmap
from PyQt5.QtCore import (QObject, QPointF, 
        QPropertyAnimation, pyqtProperty)
import sys

                              
class Ball(QObject):
    
    def __init__(self):
        super().__init__()
        
        self.pixmap_item = QGraphicsPixmapItem(QPixmap("ball.png"))
        
    def _set_pos(self, pos):
        self.pixmap_item.setPos(pos)

    pos = pyqtProperty(QPointF, fset=_set_pos)        
    
    
class Example(QGraphicsView):
    
    def __init__(self):
        super().__init__()
        
        self.initView()
        
        
    def initView(self):    
        
        self.ball = Ball()
        
        self.anim = QPropertyAnimation(self.ball, b'pos')
        self.anim.setDuration(8000)
        self.anim.setStartValue(QPointF(5, 30))
   
        self.anim.setKeyValueAt(0.3, QPointF(80, 30))                   
        self.anim.setKeyValueAt(0.5, QPointF(200, 30))
        self.anim.setKeyValueAt(0.8, QPointF(250, 250))
        
        self.anim.setEndValue(QPointF(290, 30))
                   
        self.scene = QGraphicsScene(self)
        self.scene.setSceneRect(0, 0, 300, 300)
        self.scene.addItem(self.ball.pixmap_item)
        self.setScene(self.scene)
        
        self.setWindowTitle("Ball animation")
        self.setRenderHint(QPainter.Antialiasing)        
        self.setGeometry(300, 300, 500, 350)
        
        self.anim.start()
        
        self.show()
        
                  
if __name__ == '__main__':
    
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

The example animates a ball object with QPropertyAnimation in a Graphics View Framework.

class Ball(QObject):
    
    def __init__(self):
        super().__init__()
        
        self.pixmap_item = QGraphicsPixmapItem(QPixmap("ball.png"))
        
    def _set_pos(self, pos):
        self.pixmap_item.setPos(pos)

    pos = pyqtProperty(QPointF, fset=_set_pos) 

Sice PyQt does not support multiple inheritance, we use composition technique to fullfill the previously mentioned condition.

class Example(QGraphicsView):
    
    def __init__(self):
        super().__init__()
        
        self.initView()

QGraphicsView visualizes the contents of a QGraphicsScene in a scrollable viewport.

self.anim = QPropertyAnimation(self.ball, b'pos')

We are going to animate the position property of the ball object with QPropertyAnimation.

self.anim.setDuration(8000)

The animation lasts eight seconds.

self.anim.setKeyValueAt(0.3, QPointF(80, 30))                   
self.anim.setKeyValueAt(0.5, QPointF(200, 30))
self.anim.setKeyValueAt(0.8, QPointF(250, 250))

With the setKeyValueAt() method we create a key frame at the given step with the given value. In other words, we define where the ball is located at the given step of the animation.

self.scene = QGraphicsScene(self)
self.scene.setSceneRect(0, 0, 300, 300)
self.scene.addItem(self.ball.pixmap_item)

QGraphicsScene is created and the ball is added to the scene. It provides a surface for managing a large number of 2D graphical items. Notice that we add the ball property to the scene, not the ball object.

In this tutorial, we have created animation with QPropertyAnimation.

You might also be interested in the following related tutorials: PyQt5 tutorial and Python tutorial.