如何制作连续流畅的动画?

How to make a constant and smooth animation?

所以我想制作一个动画作为 Waze 应用程序中的箭头,我希望它平滑且持续地移动。我不知道如何在 QtGraphicsView 中执行此操作。我的对象(箭头)通过 QVariantAnimation 移动,它从当前位置插值到下一个位置。结束它稍微停止。我不想要这个功能(停止),我希望我的动画连续流畅地运行。有人知道怎么做吗?

这是一个最小的可重现示例:

from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5 import QtGui
import sys
import random      

random.seed(0)
             
class Example(QtWidgets.QWidget):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.button = QtWidgets.QPushButton("Start", self)
        self.button.clicked.connect(self.doAnim)
        self.button.move(10, 10)
        self.scene = QtWidgets.QGraphicsScene()
        self.view = QtWidgets.QGraphicsView(self)
        self.view.setScene(self.scene)
        self.view.setGeometry(150, 30, 500, 800)
        self.setGeometry(300, 300, 380, 300)
        self.setWindowTitle('Animation')
        pen = QtGui.QPen()
        pen.setBrush(QtGui.QBrush(QtCore.Qt.darkBlue))
        pen.setWidth(5)
        self.scene.addEllipse(0,0,10,10, pen)
        self.show()  
        self.ellipse = self.scene.items()[0]

    def doAnim(self):
        # Every time that I click on the start buttom this animation runs 
        # and stops, 
        pos = self.ellipse.pos()
        new_pos = QtCore.QPointF(pos.x()+ random.randint(-10, 10), pos.y() +random.randint(-10, 10))
        self.anim = QtCore.QVariantAnimation()
        self.anim.setDuration(1000)
        self.anim.setStartValue(pos)
        self.anim.setEndValue(new_pos)
        self.anim.setLoopCount(-1)
        self.anim.valueChanged.connect(self.ellipse.setPos)
        self.anim.start(QtCore.QVariantAnimation.DeleteWhenStopped)

    

if __name__ == "__main__":
    import sys 
    app = QtWidgets.QApplication(sys.argv)
    ex = Example()
    ex.show()
    app.exec_()

简单的解决方案是连接到动画的 finished 信号,如果有新的目标位置可用,则再次设置 start/end 值。

from random import randrange
from PyQt5 import QtCore, QtGui, QtWidgets

class Example(QtWidgets.QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        layout = QtWidgets.QGridLayout(self)
        self.startButton = QtWidgets.QPushButton("Start")
        layout.addWidget(self.startButton)
        self.addPointButton = QtWidgets.QPushButton("Add target point")
        layout.addWidget(self.addPointButton, 0, 1)

        self.view = QtWidgets.QGraphicsView()
        layout.addWidget(self.view, 1, 0, 1, 2)
        self.scene = QtWidgets.QGraphicsScene()
        self.view.setScene(self.scene)
        self.scene.setSceneRect(-10, -10, 640, 480)

        pen = QtGui.QPen(QtCore.Qt.darkBlue, 5)
        self.ellipse = self.scene.addEllipse(0, 0, 10, 10, pen)

        self.queue = []

        self.startButton.clicked.connect(self.begin)
        self.addPointButton.clicked.connect(self.addPoint)
        self.anim = QtCore.QVariantAnimation()
        self.anim.setDuration(1000)
        self.anim.valueChanged.connect(self.ellipse.setPos)
        self.anim.setStartValue(self.ellipse.pos())
        self.anim.finished.connect(self.checkPoint)

    def begin(self):
        self.startButton.setEnabled(False)
        self.addPoint()

    def addPoint(self):
        self.queue.append(QtCore.QPointF(randrange(600), randrange(400)))
        self.checkPoint()

    def checkPoint(self):
        if not self.anim.state() and self.queue:
            if self.anim.currentValue():
                # a valid currentValue is only returned when the animation has
                # been started at least once
                self.anim.setStartValue(self.anim.currentValue())
            self.anim.setEndValue(self.queue.pop(0))
            self.anim.start()
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5 import QtGui
import sys
import random     
import queue  
import time 

class Position(queue.Queue):

    def __init__(self, *args, **kwargs):
        super(Position, self).__init__(*args, **kwargs)


class Worker(QtCore.QThread):
    pos_signal = QtCore.pyqtSignal(QtCore.QPointF)
    def __init__(self, rect, parent=None, *args, **kwargs):
        super().__init__(parent)
        random.seed(0)
        self.pos = QtCore.QPointF(0, 0)
        self.rect: QtCore.QRectF = rect 

    def run(self) -> None:
        new_pos = QtCore.QPointF(self.pos.x()+ random.randint(-100, 100), self.pos.y() +random.randint(-100, 100))
        if not self.rect.contains(new_pos): 
            self.run()
        else: 
            self.pos = new_pos
            self.pos_signal.emit(new_pos)
            time.sleep(0.1)
            self.run()

             
class Example(QtWidgets.QWidget):
    next_signal = QtCore.pyqtSignal()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.button = QtWidgets.QPushButton("Start", self)
        self.button.clicked.connect(self.doAnim)
        self.scene = QtWidgets.QGraphicsScene()
        self.view = QtWidgets.QGraphicsView(self)
        self.view.setScene(self.scene)
        self.view.setGeometry(100, 100, 500, 800)
        self.setGeometry(300, 300, 800, 800)
        pen = QtGui.QPen()
        pen.setBrush(QtGui.QBrush(QtCore.Qt.darkBlue))
        pen.setWidth(5)
        self.scene.addEllipse(0,0,20,10, pen)
        self.ellipse = self.scene.items()[0]
        self.rect = QtCore.QRectF(0,0, 200, 200)
        self.scene.addRect(self.rect, pen)
        self.positions = Position()
        self.producer = Worker(rect=self.rect)
        self.producer.pos_signal.connect(self.positions.put)
        self.producer.start()
        self.old = QtCore.QPointF(0, 0)
        self.new = None 
        

    def ready(self):
        self.next_signal.emit()
        self.old = self.new 
        self.doAnim()

    def doAnim(self):
        # Every time that I click on the start buttom this animation runs 
        # and stops, 
        self.sequential_animation = QtCore.QSequentialAnimationGroup(self)
        self.new = self.positions.get()
        self.anim = QtCore.QVariantAnimation()
        self.anim.setDuration(1000)
        self.anim.setStartValue(self.old)
        self.anim.setEndValue(self.new)
        self.anim.valueChanged.connect(self.ellipse.setPos)
        self.anim.finished.connect(self.ready)
        self.sequential_animation.addAnimation(self.anim)
        self.sequential_animation.start(QtCore.QSequentialAnimationGroup.DeleteWhenStopped)
        # self.anim.start(QtCore.QVariantAnimation.DeleteWhenStopped)

    

if __name__ == "__main__":
    import sys 
    app = QtWidgets.QApplication(sys.argv)
    ex = Example()
    ex.show()
    app.exec_()