【问题标题】:A question I meet when adding animation on QGraphicsItem在 QGraphicsItem 上添加动画时遇到的一个问题
【发布时间】:2019-09-12 20:11:02
【问题描述】:

我想在 QGraphicsItem 上使用 QPropertyAnimation,希望 rect 项可以从 point(100, 30) 移动到 point(100, 90)。但是为什么矩形会在窗口的右侧移动呢? x 坐标 100 应该使矩形根据场景的大小在中间移动。

这是我的代码:

import sys
from PyQt5.QtCore import QPropertyAnimation, QPointF, QRectF
from PyQt5.QtWidgets import QApplication, QGraphicsEllipseItem, QGraphicsScene, QGraphicsView, \
                            QGraphicsObject


class CustomRect(QGraphicsObject):
    def __init__(self):
        super(CustomRect, self).__init__()

    def boundingRect(self):
        return QRectF(100, 30, 100, 30)

    def paint(self, painter, styles, widget=None):
        painter.drawRect(self.boundingRect())


class Demo(QGraphicsView):
    def __init__(self):
        super(Demo, self).__init__()
        self.resize(300, 300)

        self.scene = QGraphicsScene()
        self.scene.setSceneRect(0, 0, 300, 300)

        self.rect = CustomRect()
        self.ellipse = QGraphicsEllipseItem()
        self.ellipse.setRect(100, 180, 100, 50)

        self.scene.addItem(self.rect)
        self.scene.addItem(self.ellipse)

        self.setScene(self.scene)

        self.animation = QPropertyAnimation(self.rect, b'pos')
        self.animation.setDuration(1000)
        self.animation.setStartValue(QPointF(100, 30))
        self.animation.setEndValue(QPointF(100, 90))
        self.animation.setLoopCount(-1)
        self.animation.start()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

【问题讨论】:

    标签: python pyqt pyqt5 qgraphicsitem


    【解决方案1】:

    他们好像不知道Graphics View Framework的不同坐标系。

    在这个系统中至少有以下坐标系:

    • window(viewport()) 的坐标系,其中 (0, 0) 始终位于窗口的左上角。

    • 场景的坐标系,这是相对于某个预先确定的点。

    • 每个item的坐标坐标系,这个坐标系被paint()方法用来绘制,boundingRect()和shape()方法用来获取item的边缘。

    你还得有另外一个概念,一个item的位置是相对于parent,如果他有的话,如果他没有,它是相对于场景的。

    类比

    为了解释不同的坐标系,我使用了使用相机记录场景的类比。

    • QGraphicsView 将是相机的屏幕。
    • QGraphicsScene 是记录的场景,所以点 (0, 0) 是方便的点。
    • QGraphicsItem 是场景的元素,它们的位置可以相对于其他项目或场景,例如我们可以考虑演员的鞋子相对于演员的位置,或者项目可以是同一个演员.

    基于以上我将解释发生了什么,我们将给出几种解决方案。

    rect 项没有父项,默认情况下,项的位置是 (0, 0),因此此时项的坐标系和场景重合,因此 boundingRect 将在视觉上定义位置,就像您放置的那样QRectF(100, 30, 100, 30) 这将被绘制在恰好在场景中相同的位置。但是,当您应用动画时,要做的第一件事是将项目的位置设置为 (100, 30),这样由于场景和项目的坐标系不匹配,一个会从另一个偏移,所以boundingRect不再匹配场景的QRectF(100, 30, 100, 30),而是会以相同的因子移动(只是因为有位移,没有缩放或旋转),矩形将是QRectF( 200, 60, 100, 30) 并且相对于始终在 QRect(100, 180, 100, 50) 中的椭圆,因此矩形自 200>100 起位于右侧,并且自 60


    因此,如果您希望矩形位于椭圆之上,则至少有两种解决方案:

    1. 修改 boundingRect 使其位于位置 0,0 以便与动画引起的位移相匹配:
    import sys
    from PyQt5 import QtCore, QtWidgets
    
    
    class CustomRect(QtWidgets.QGraphicsObject):
        def boundingRect(self):
            return QtCore.QRectF(0, 0, 100, 30)  # <---
    
        def paint(self, painter, styles, widget=None):
            painter.drawRect(self.boundingRect())
    
    
    class Demo(QtWidgets.QGraphicsView):
        def __init__(self):
            super(Demo, self).__init__()
            self.resize(300, 300)
    
            self.scene = QtWidgets.QGraphicsScene()
            self.scene.setSceneRect(0, 0, 300, 300)
    
            self.rect = CustomRect()
            self.ellipse = QtWidgets.QGraphicsEllipseItem()
            self.ellipse.setRect(100, 180, 100, 50)
            self.scene.addItem(self.rect)
            self.scene.addItem(self.ellipse)
    
            self.setScene(self.scene)
    
            self.animation = QtCore.QPropertyAnimation(
                self.rect,
                b"pos",
                duration=1000,
                startValue=QtCore.QPointF(100, 30),
                endValue=QtCore.QPointF(100, 90),
                loopCount=-1,
            )
            self.animation.start()
    
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        demo = Demo()
        demo.show()
        sys.exit(app.exec_())
    
    1. 修改动画使其不产生位移:
    import sys
    from PyQt5 import QtCore, QtWidgets
    
    
    class CustomRect(QtWidgets.QGraphicsObject):
        def boundingRect(self):
            return QtCore.QRectF(100, 30, 100, 30)
    
        def paint(self, painter, styles, widget=None):
            painter.drawRect(self.boundingRect())
    
    
    class Demo(QtWidgets.QGraphicsView):
        def __init__(self):
            super(Demo, self).__init__()
            self.resize(300, 300)
    
            self.scene = QtWidgets.QGraphicsScene()
            self.scene.setSceneRect(0, 0, 300, 300)
    
            self.rect = CustomRect()
            self.ellipse = QtWidgets.QGraphicsEllipseItem()
            self.ellipse.setRect(100, 180, 100, 50)
            self.scene.addItem(self.rect)
            self.scene.addItem(self.ellipse)
    
            self.setScene(self.scene)
    
            self.animation = QtCore.QPropertyAnimation(
                self.rect,
                b"pos",
                duration=1000,
                startValue=QtCore.QPointF(0, 0), # <---
                endValue=QtCore.QPointF(0, 60), # <---
                loopCount=-1,
            )
            self.animation.start()
    
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        demo = Demo()
        demo.show()
        sys.exit(app.exec_())
    

    【讨论】:

    • 谢谢@eyllanesc,你的回答一如既往的好。那么你的意思是动画是基于项目的坐标而不是场景的坐标吗?我实际上知道如何解决这个问题,但就是无法理解动画使用的坐标。
    • @just_be_happy 我没有这么说,我说的是视觉上观察到的是一组变换的结果。恕我直言,我认为为了简化事情 boundingRect 必须始终从 (0,0) 开始,因为这是相对于同一项目的坐标系,因为这避免了复杂的计算,这在我的第一个解决方案中很明显。
    • @just_be_happy 动画不知道或有兴趣知道不同类型的坐标系,它只修改属性“pos”。在这种情况下,位置是关于场景的,但是您的项目被绘制在一个已经在其自身上位移QRectF (100, 30, 100, 30)的矩形上,并且
    • @just_be_happy [cont.] 你也在相对于场景移动它: QPointF( 100, 30) 动画后的片刻,绘图从 QRectF + 位移 (100, 30) QPoint 的 (100, 30) = (200, 60) 所以它在椭圆 (100, 180, 100, 50) 的右侧和上方。只需始终使用 QRectF(0, 0, w, h) 实现 boundingRect 并使用 setPos 移动它。
    • 我想如果 boundingRect 是 (100, 30, 100, 30) 并且如果我想让矩形向右移动,我需要使用 QPointF(200, 30) 和 QPointF(200 , 90)。不知道有位移。谢谢eyllanesc。我会将您的答案标记为正确。但是为什么会存在这种位移呢?
    【解决方案2】:

    试试看:

    import sys
    from PyQt5.QtCore import QPropertyAnimation, QPointF, QRectF
    from PyQt5.QtWidgets import QApplication, QGraphicsEllipseItem, QGraphicsScene, QGraphicsView, \
                                QGraphicsObject
    
    
    class CustomRect(QGraphicsObject):
        def __init__(self):
            super(CustomRect, self).__init__()
    
        def boundingRect(self):
    #        return QRectF(100, 30, 100, 30)
            return QRectF(0, 0, 100, 30)                          # +++
    
        def paint(self, painter, styles, widget=None):
            painter.drawRect(self.boundingRect())
    
    
    class Demo(QGraphicsView):
        def __init__(self):
            super(Demo, self).__init__()
            self.resize(300, 300)
    
            self.scene = QGraphicsScene()
            self.scene.setSceneRect(0, 0, 300, 300)
    
            self.rect = CustomRect()
            self.ellipse = QGraphicsEllipseItem()
            self.ellipse.setRect(100, 180, 100, 50)
    
            self.scene.addItem(self.rect)
            self.scene.addItem(self.ellipse)
    
            self.setScene(self.scene)
    
            self.animation = QPropertyAnimation(self.rect, b'pos')
            self.animation.setDuration(3000)
            self.animation.setStartValue(QPointF(100, 30))
            self.animation.setEndValue(QPointF(100, 90))
            self.animation.setLoopCount(-1)
            self.animation.start()
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        demo = Demo()
        demo.show()
        sys.exit(app.exec_())
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-07-02
      • 1970-01-01
      • 2016-08-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多