PyQt5.QtCore中的 QPropertyAnimation可以实现动画功能。
下面第一个例子通过将一个QLabel对象移动和放大来实现简单的动画:
import sysfrom PyQt5.QtCore import QPropertyAnimation, QRect, QEasingCurvefrom PyQt5 import QtGuifrom PyQt5.QtWidgets import QApplication, QWidget, QLabel
class AnimationDemo(QWidget): def __init__(self): super().__init__() self.resize(800, 800) self.label = QLabel('Move', self) pixmap = QtGui.QPixmap('sight.jpg') self.label.setPixmap(pixmap) self.label.setGeometry(0, 0, 300, 300) self.animation = QPropertyAnimation(self.label, b'geometry') # 实例化一个动画对象 # 参数1 动画要操作的对象 # 参数2 要改变的属性 geometry位置和大小 #注意:字节类型 #pos---位置动画---QPoint #size---大小动画---QSize #geometry----位置+大小动画----QRect #windowOpacity---窗口的透明度(0.0是透明的 1.0是不透明) self.animation.setDuration(5000) # 设置动画持续时间。单位毫秒 self.animation.setStartValue(QRect(0, 0, 300, 300)) #动画开始时的位置和大小 self.animation.setEndValue(QRect(500, 500, 500, 500)) #动画结束时的位置和大小 #self.animation.setEasingCurve(QEasingCurve.InBounce) #设置缓和曲线 self.animation.setLoopCount(-1) #设置循环次数 #-1 无限循环 #0 不循环 #正数 循环次数 self.animation.start()#动画开始 if __name__ == "__main__": app = QApplication(sys.argv) demo = AnimationDemo() demo.show() sys.exit(app.exec_())
第二个例子来自PyQt4官方demo(现已升级为PyQt5版本)。代码实现了一个火柴人,他开心时可以跳跃、可以舞蹈,不开心时可以躺地上……
代码有点复杂:
#!/usr/bin/env python################################################################################# Copyright (C) 2010 Riverbank Computing Limited.## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).## All rights reserved.
import mathfrom PyQt5 import QtCore, QtWidgets, QtGuiimport stickman_qrc
class Node(QtWidgets.QGraphicsObject): positionChanged = QtCore.pyqtSignal() def __init__(self, pos, parent=None): super().__init__(parent) self.m_dragging = False self.setPos(pos) self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges) def boundingRect(self): return QtCore.QRectF(-6.0, -6.0, 12.0, 12.0) def paint(self, painter, option, widget): painter.setPen(QtCore.Qt.white) painter.drawEllipse(QtCore.QPointF(0.0, 0.0), 5.0, 5.0) def itemChange(self, change, value): if change == QtWidgets.QGraphicsItem.ItemPositionChange: self.positionChanged.emit() return super(Node, self).itemChange(change, value) def mousePressEvent(self, event): self.m_dragging = True def mouseMoveEvent(self, event): if self.m_dragging: self.setPos(self.mapToParent(event.pos())) def mouseReleaseEvent(self, event): self.m_dragging = False
Coords = ( # Head: 0 (0.0, -150.0), # Body pentagon, top->bottom, left->right: 1 - 5 (0.0, -100.0), (-50.0, -50.0), (50.0, -50.0), (-25.0, 50.0), (25.0, 50.0), # Right arm: 6 - 7 (-100.0, 0.0), (-125.0, 50.0), # Left arm: 8 - 9 (100.0, 0.0), (125.0, 50.0), # Lower body: 10 - 11 (-35.0, 75.0), (35.0, 75.0), # Right leg: 12 - 13 (-25.0, 200.0), (-30.0, 300.0), # Left leg: 14 - 15 (25.0, 200.0), (30.0, 300.0))
Bones = ( # Neck. (0, 1), # Body. (1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5), # Right arm. (2, 6), (6, 7), # Left arm. (3, 8), (8, 9), # Lower body. (4, 10), (4, 11), (5, 10), (5, 11), (10, 11), # Right leg. (10, 12), (12, 13), # Left leg. (11, 14), (14, 15))
/*
* 提示:该行代码过长,系统自动注释不进行高亮。一键复制会移除系统注释
* class StickMan(QtWidgets.QGraphicsObject): def __init__(self): super().__init__() self.m_sticks = True self.m_isDead = False self.m_pixmap = QtGui.QPixmap('images/head.png') self.m_penColor = QtGui.QColor(QtCore.Qt.white) self.m_fillColor = QtGui.QColor(QtCore.Qt.black) # Set up start position of limbs. self.m_nodes = [] for x, y in Coords: node = Node(QtCore.QPointF(x, y), self) node.positionChanged.connect(self.childPositionChanged) self.m_nodes.append(node) self.m_perfectBoneLengths = [] for n1, n2 in Bones: node1 = self.m_nodes[n1] node2 = self.m_nodes[n2] dist = node1.pos() - node2.pos() self.m_perfectBoneLengths.append(math.hypot(dist.x(), dist.y())) self.startTimer(10) def childPositionChanged(self): self.prepareGeometryChange() def setDrawSticks(self, on): self.m_sticks = on for node in self.m_nodes: node.setVisible(on) def drawSticks(self): return self.m_sticks def boundingRect(self): # Account for head radius of 50.0 plus pen which is 5.0. return self.childrenBoundingRect().adjusted(-55.0, -55.0, 55.0, 55.0) def nodeCount(self): return len(self.m_nodes) def node(self, idx): if idx >= 0 and idx < len(self.m_nodes): return self.m_nodes[idx] return None def timerEvent(self, e): self.update() def stabilize(self): threshold = 0.001 for i, (n1, n2) in enumerate(Bones): node1 = self.m_nodes[n1] node2 = self.m_nodes[n2] pos1 = node1.pos() pos2 = node2.pos() dist = pos1 - pos2 length = math.hypot(dist.x(), dist.y()) diff = (length - self.m_perfectBoneLengths[i]) / length p = dist * (0.5 * diff) if p.x() > threshold and p.y() > threshold: pos1 -= p pos2 += p node1.setPos(pos1) node2.setPos(pos2) def posFor(self, idx): return self.m_nodes[idx].pos() @QtCore.pyqtProperty(QtGui.QColor) def penColor(self): return QtGui.QColor(self.m_penColor) @penColor.setter def penColor(self, color): self.m_penColor = QtGui.QColor(color) @QtCore.pyqtProperty(QtGui.QColor) def fillColor(self): return QtGui.QColor(self.m_fillColor) @fillColor.setter def fillColor(self, color): self.m_fillColor = QtGui.QColor(color) @QtCore.pyqtProperty(bool) def isDead(self): return self.m_isDead @isDead.setter def isDead(self, isDead): self.m_isDead = isDead def paint(self, painter, option, widget): self.stabilize() if self.m_sticks: painter.setPen(QtCore.Qt.white) for n1, n2 in Bones: node1 = self.m_nodes[n1] node2 = self.m_nodes[n2] painter.drawLine(node1.pos(), node2.pos()) else: # First bone is neck and will be used for head. path = QtGui.QPainterPath() path.moveTo(self.posFor(0)) path.lineTo(self.posFor(1)) # Right arm. path.lineTo(self.posFor(2)) path.lineTo(self.posFor(6)) path.lineTo(self.posFor(7)) # Left arm. path.moveTo(self.posFor(3)) path.lineTo(self.posFor(8)) path.lineTo(self.posFor(9)) # Body. path.moveTo(self.posFor(2)) path.lineTo(self.posFor(4)) path.lineTo(self.posFor(10)) path.lineTo(self.posFor(11)) path.lineTo(self.posFor(5)) path.lineTo(self.posFor(3)) path.lineTo(self.posFor(1)) # Right leg. path.moveTo(self.posFor(10)) path.lineTo(self.posFor(12)) path.lineTo(self.posFor(13)) # Left leg. path.moveTo(self.posFor(11)) path.lineTo(self.posFor(14)) path.lineTo(self.posFor(15)) painter.setPen(QtGui.QPen(self.m_penColor, 5.0, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap)) painter.drawPath(path) n1, n2 = Bones[0] node1 = self.m_nodes[n1] node2 = self.m_nodes[n2] dist = node2.pos() - node1.pos() sinAngle = dist.x() / math.hypot(dist.x(), dist.y()) angle = math.degrees(math.asin(sinAngle)) headPos = node1.pos() painter.translate(headPos) painter.rotate(-angle) painter.setBrush(self.m_fillColor) painter.drawEllipse(QtCore.QPointF(0, 0), 50.0, 50.0) painter.setBrush(self.m_penColor) painter.setPen(QtGui.QPen(self.m_penColor, 2.5, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap)) # Eyes. if self.m_isDead: painter.drawLine(-30.0, -30.0, -20.0, -20.0) painter.drawLine(-20.0, -30.0, -30.0, -20.0) painter.drawLine(20.0, -30.0, 30.0, -20.0) painter.drawLine(30.0, -30.0, 20.0, -20.0) else: painter.drawChord(QtCore.QRectF(-30.0, -30.0, 25.0, 70.0), 30.0 * 16, 120.0 * 16) painter.drawChord(QtCore.QRectF(5.0, -30.0, 25.0, 70.0), 30.0 * 16, 120.0 * 16) # Mouth. if self.m_isDead: painter.drawLine(-28.0, 2.0, 29.0, 2.0) else: painter.setBrush(QtGui.QColor(128, 0, 64 )) painter.drawChord(QtCore.QRectF(-28.0, 2.0 - 55.0 / 2.0, 57.0, 55.0), 0.0, -180.0 * 16) # Pupils. if not self.m_isDead: painter.setPen(QtGui.QPen(self.m_fillColor, 1.0, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap)) painter.setBrush(self.m_fillColor) painter.drawEllipse(QtCore.QPointF(-12.0, -25.0), 5.0, 5.0) painter.drawEllipse(QtCore.QPointF(22.0, -25.0), 5.0, 5.0)
*/
class GraphicsView(QtWidgets.QGraphicsView): keyPressed = QtCore.pyqtSignal(int) def keyPressEvent(self, e): if e.key() == QtCore.Qt.Key_Escape: self.close() self.keyPressed.emit(QtCore.Qt.Key(e.key()))
class Frame(object): def __init__(self): self.m_nodePositions = [] def nodeCount(self): return len(self.m_nodePositions) def setNodeCount(self, nodeCount): while nodeCount > len(self.m_nodePositions): self.m_nodePositions.append(QtCore.QPointF()) while nodeCount < len(self.m_nodePositions): self.m_nodePositions.pop() def nodePos(self, idx): return QtCore.QPointF(self.m_nodePositions[idx]) def setNodePos(self, idx, pos): self.m_nodePositions[idx] = QtCore.QPointF(pos)
class Animation(object): def __init__(self): self.m_currentFrame = 0 self.m_frames = [Frame()] self.m_name = '' def setTotalFrames(self, totalFrames): while len(self.m_frames) < totalFrames: self.m_frames.append(Frame()) while totalFrames < len(self.m_frames): self.m_frames.pop() def totalFrames(self): return len(self.m_frames) def setCurrentFrame(self, currentFrame): self.m_currentFrame = max(min(currentFrame, self.totalFrames() - 1), 0) def currentFrame(self): return self.m_currentFrame def setNodeCount(self, nodeCount): frame = self.m_frames[self.m_currentFrame] frame.setNodeCount(nodeCount) def nodeCount(self): frame = self.m_frames[self.m_currentFrame] return frame.nodeCount() def setNodePos(self, idx, pos): frame = self.m_frames[self.m_currentFrame] frame.setNodePos(idx, pos) def nodePos(self, idx): frame = self.m_frames[self.m_currentFrame] return frame.nodePos(idx) def name(self): return self.m_name def setName(self, name): self.m_name = name def save(self, device): stream = QtCore.QDataStream(device) stream.writeQString(self.m_name) stream.writeInt(len(self.m_frames)) for frame in self.m_frames: stream.writeInt(frame.nodeCount()) for i in range(frame.nodeCount()): stream << frame.nodePos(i) def load(self, device): self.m_frames = [] stream = QtCore.QDataStream(device) self.m_name = stream.readQString() frameCount = stream.readInt() for i in range(frameCount): nodeCount = stream.readInt() frame = Frame() frame.setNodeCount(nodeCount) for j in range(nodeCount): pos = QtCore.QPointF() stream >> pos frame.setNodePos(j, pos) self.m_frames.append(frame)
class KeyPressTransition(QtCore.QSignalTransition): def __init__(self, receiver, key, target=None): super(KeyPressTransition, self).__init__(receiver.keyPressed) self.m_key = key if target is not None: self.setTargetState(target) def eventTest(self, e): if super(KeyPressTransition, self).eventTest(e): key = e.arguments()[0] return key == self.m_key return False
class LightningStrikesTransition(QtCore.QEventTransition): def __init__(self, target): super().__init__() self.setEventSource(self) self.setEventType(QtCore.QEvent.Timer) self.setTargetState(target) QtCore.qsrand(QtCore.QDateTime.currentDateTime().toTime_t()) self.startTimer(1000) def eventTest(self, e): return (super(LightningStrikesTransition, self).eventTest(e) and (QtCore.qrand() % 50) == 0)
class LifeCycle(object): def __init__(self, stickMan, keyReceiver): self.m_stickMan = stickMan self.m_keyReceiver = keyReceiver # Create animation group to be used for all transitions. self.m_animationGroup = QtCore.QParallelAnimationGroup() stickManNodeCount = self.m_stickMan.nodeCount() self._pas = [] for i in range(stickManNodeCount): pa = QtCore.QPropertyAnimation(self.m_stickMan.node(i), b'pos') self._pas.append(pa) self.m_animationGroup.addAnimation(pa) # Set up intial state graph. self.m_machine = QtCore.QStateMachine() self.m_machine.addDefaultAnimation(self.m_animationGroup) self.m_alive = QtCore.QState(self.m_machine) self.m_alive.setObjectName('alive') # Make it blink when lightning strikes before entering dead animation. lightningBlink = QtCore.QState(self.m_machine) lightningBlink.assignProperty(self.m_stickMan.scene(),'backgroundBrush', QtCore.Qt.white) lightningBlink.assignProperty(self.m_stickMan, 'penColor',QtCore.Qt.black) lightningBlink.assignProperty(self.m_stickMan, 'fillColor',QtCore.Qt.white) lightningBlink.assignProperty(self.m_stickMan, 'isDead', True) timer = QtCore.QTimer(lightningBlink) timer.setSingleShot(True) timer.setInterval(100) lightningBlink.entered.connect(timer.start) lightningBlink.exited.connect(timer.stop) self.m_dead = QtCore.QState(self.m_machine) self.m_dead.assignProperty(self.m_stickMan.scene(), 'backgroundBrush',QtCore.Qt.black) self.m_dead.assignProperty(self.m_stickMan, 'penColor',QtCore.Qt.white) self.m_dead.assignProperty(self.m_stickMan, 'fillColor',QtCore.Qt.black) self.m_dead.setObjectName('dead') # Idle state (sets no properties). self.m_idle = QtCore.QState(self.m_alive) self.m_idle.setObjectName('idle') self.m_alive.setInitialState(self.m_idle) # Lightning strikes at random. self.m_alive.addTransition(LightningStrikesTransition(lightningBlink)) lightningBlink.addTransition(timer.timeout, self.m_dead) self.m_machine.setInitialState(self.m_alive)
def setDeathAnimation(self, fileName): deathAnimation = self.makeState(self.m_dead, fileName) self.m_dead.setInitialState(deathAnimation) def start(self): self.m_machine.start() def addActivity(self, fileName, key): state = self.makeState(self.m_alive, fileName) self.m_alive.addTransition(KeyPressTransition(self.m_keyReceiver, key, state)) def makeState(self, parentState, animationFileName): topLevel = QtCore.QState(parentState) animation = Animation() file = QtCore.QFile(animationFileName) if file.open(QtCore.QIODevice.ReadOnly): animation.load(file) frameCount = animation.totalFrames() previousState = None for i in range(frameCount): animation.setCurrentFrame(i) frameState = QtCore.QState(topLevel) nodeCount = animation.nodeCount() for j in range(nodeCount): frameState.assignProperty(self.m_stickMan.node(j), b'pos', animation.nodePos(j)) frameState.setObjectName('frame %d' % i) if previousState is None: topLevel.setInitialState(frameState) else: previousState.addTransition(previousState.propertiesAssigned, frameState) previousState = frameState previousState.addTransition(previousState.propertiesAssigned, topLevel.initialState()) return topLevel
if __name__ == '__main__': import sys app = QtWidgets.QApplication(sys.argv) stickMan = StickMan() stickMan.setDrawSticks(False) textItem = QtWidgets.QGraphicsTextItem() textItem.setHtml("<font color=\"white\"><b>Stickman</b>" "<p>" "Tell the stickman what to do!" "</p>" "<p><i>" "<li>Press <font color=\"purple\">J</font> to make the stickman jump.</li>" "<li>Press <font color=\"purple\">D</font> to make the stickman dance.</li>" "<li>Press <font color=\"purple\">C</font> to make him chill out.</li>" "<li>When you are done, press <font color=\"purple\">Escape</font>.</li>" "</i></p>" "<p>If he is unlucky, the stickman will get struck by lightning, and never jump, dance or chill out again." "</p></font>") w = textItem.boundingRect().width() stickManBoundingRect = stickMan.mapToScene(stickMan.boundingRect()).boundingRect() textItem.setPos(-w / 2.0, stickManBoundingRect.bottom() + 25.0) scene = QtWidgets.QGraphicsScene() scene.addItem(stickMan) scene.addItem(textItem) scene.setBackgroundBrush(QtCore.Qt.black) view = GraphicsView() view.setRenderHints(QtGui.QPainter.Antialiasing) view.setTransformationAnchor(QtWidgets.QGraphicsView.NoAnchor) view.setScene(scene) view.show() view.setFocus() # Make enough room in the scene for stickman to jump and die. sceneRect = scene.sceneRect() view.resize(sceneRect.width() + 100, sceneRect.height() + 100) view.setSceneRect(sceneRect) cycle = LifeCycle(stickMan, view) cycle.setDeathAnimation(':/animations/dead') cycle.addActivity(':/animations/jumping', QtCore.Qt.Key_J) cycle.addActivity(':/animations/dancing', QtCore.Qt.Key_D) cycle.addActivity(':/animations/chilling', QtCore.Qt.Key_C) cycle.start() sys.exit(app.exec_())
本文分享自 Python可视化编程机器学习OpenCV 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!