所以我试图在PyQt中学习动画,在我可以在线找到的所有示例中,它们似乎都使用self.update()
或self.repaint()
方法来增加动画。这意味着基本上代码必须擦除,然后为每一帧重新绘制整个小部件,即使我打算动画的大部分内容都是静态的。
例如下面的代码,这将生成一个循环进度饼图。重要的一点是ProgressMeter
(第一类)下的paint()
方法:对于动画中的每一帧,此示例绘制背景、实际进度饼图和百分比指示器。
如果我将代码更改为类似的代码
if self.angle > 120:
# do not draw background
则在120帧之后不再绘制背景。
这似乎非常低效,因为(逻辑上)背景应该只画一次,不是吗?
对于这样的动画,你有什么建议?
附录:我在这个网站上潜伏了很多例子和代码,但已经很久没有发布了。如果我没有正确遵守礼仪等,请告诉我。
import sys
from PyQt4 import QtGui, QtCore
class ProgressMeter(QtGui.QGraphicsItem):
def __init__(self, parent):
super(ProgressMeter, self).__init__()
self.parent = parent
self.angle = 0
self.per = 0
def boundingRect(self):
return QtCore.QRectF(0, 0, self.parent.width(),
self.parent.height())
def increment(self):
self.angle += 1
self.per = int(self.angle / 3.6)
if self.angle > 360:
return False
else:
return True
def paint(self, painter, option, widget):
self.drawBackground(painter, widget)
self.drawMeter(painter, widget)
self.drawText(painter)
def drawBackground(self, painter, widget):
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(QtCore.Qt.NoPen)
p1 = QtCore.QPointF(80, 80)
g = QtGui.QRadialGradient(p1 * 0.2, 80 * 1.1)
g.setColorAt(0.0, widget.palette().light().color())
g.setColorAt(1.0, widget.palette().dark().color())
painter.setBrush(g)
painter.drawEllipse(0, 0, 80, 80)
p2 = QtCore.QPointF(40, 40)
g = QtGui.QRadialGradient(p2, 70 * 1.3)
g.setColorAt(0.0, widget.palette().midlight().color())
g.setColorAt(1.0, widget.palette().dark().color())
painter.setBrush(g)
painter.drawEllipse(7.5, 7.5, 65, 65)
def drawMeter(self, painter, widget):
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(widget.palette().highlight().color())
painter.drawPie(7.5, 7.5, 65, 65, 0, -self.angle * 16)
def drawText(self, painter):
text = "%d%%" % self.per
font = painter.font()
font.setPixelSize(11)
painter.setFont(font)
brush = QtGui.QBrush(QtGui.QColor("#000000"))
pen = QtGui.QPen(brush, 1)
painter.setPen(pen)
# size = painter.fontMetrics().size(QtCore.Qt.TextSingleLine, text)
painter.drawText(0, 0, 80, 80,
QtCore.Qt.AlignCenter, text)
class MyView(QtGui.QGraphicsView):
def __init__(self):
super(MyView, self).__init__()
self.initView()
self.setupScene()
self.setupAnimation()
self.setGeometry(300, 150, 250, 250)
def initView(self):
self.setWindowTitle("Progress meter")
self.setRenderHint(QtGui.QPainter.Antialiasing)
policy = QtCore.Qt.ScrollBarAlwaysOff
self.setVerticalScrollBarPolicy(policy)
self.setHorizontalScrollBarPolicy(policy)
self.setBackgroundBrush(self.palette().window())
self.pm = ProgressMeter(self)
self.pm.setPos(55, 55)
def setupScene(self):
self.scene = QtGui.QGraphicsScene(self)
self.scene.setSceneRect(0, 0, 250, 250)
self.scene.addItem(self.pm)
self.setScene(self.scene)
def setupAnimation(self):
self.timer = QtCore.QTimeLine()
self.timer.setLoopCount(0)
self.timer.setFrameRange(0, 100)
self.animation = QtGui.QGraphicsItemAnimation()
self.animation.setItem(self.pm)
self.animation.setTimeLine(self.timer)
self.timer.frameChanged[int].connect(self.doStep)
self.timer.start()
def doStep(self, i):
if not self.pm.increment():
self.timer.stop()
self.pm.update()
app = QtGui.QApplication([])
view = MyView()
view.show()
sys.exit(app.exec_())
Qt关于QWidget重新绘制插槽的文档中写道:
通过立即调用paintEvent()直接重新绘制小部件,除非更新被禁用或小部件被隐藏。如果需要立即重新绘制,例如在动画期间,我们建议仅使用重新绘制()。在几乎所有情况下,update()都更好,因为它允许Qt优化速度并最大限度地减少闪烁。警告:如果您在函数中调用paint(),而函数本身可能是从paintEvent()调用的,则可能会得到无限递归。update()函数从不引起递归
这应该会给你一个关于何时使用或不重新绘制或更新插槽的答案。
关于制作动画,我建议你也看看Qt4的动画框架或Qt5的动画帧,这是在Qt上制作小部件动画的一种非常强大的方式。