64.md 13.1 KB
Newer Older
W
wizardforcel 已提交
1
# PyQt 中的`QPropertyAnimation`
W
wizardforcel 已提交
2 3 4

> 原文: [http://zetcode.com/pyqt/qpropertyanimation/](http://zetcode.com/pyqt/qpropertyanimation/)

W
wizardforcel 已提交
5
PyQt 中的 QPropertyAnimation 显示了如何使用`QPropertyAnimation`在 PyQt 中创建动画。 在示例中,我们对对象的大小,颜色和位置进行了动画处理。 来源和球图像可以在作者的 Github [仓库](https://github.com/janbodnar/pyqt-qpropertyanimation)中找到。
W
wizardforcel 已提交
6

W
wizardforcel 已提交
7
## `QPropertyAnimation`
W
wizardforcel 已提交
8 9 10

`QPropertyAnimation`内插 PyQt 属性。 声明属性的类必须为为`QObject`

W
wizardforcel 已提交
11
## `QPropertyAnimation`方法
W
wizardforcel 已提交
12 13 14 15 16

下表显示了一些重要的`QPropertyAnimation`方法:

| 名称 | 描述 |
| --- | --- |
W
wizardforcel 已提交
17 18 19 20 21 22 23
| `start()` | 开始动画 |
| `stop()` | 终止动画 |
| `setStartValue()` | 设置动画的起始值 |
| `setEndValue()` | 设置动画的结束值 |
| `setDuration()` | 设置动画的持续时间,以毫秒为单位 |
| `setKeyValueAt()` | 在给定步骤以给定值创建关键帧 |
| `setLoopCount()` | 设置动画的重复次数 |
W
wizardforcel 已提交
24

W
wizardforcel 已提交
25
## 使用`QPropertyAnimation`设置动画大小
W
wizardforcel 已提交
26 27 28 29 30

在第一个示例中,我们为小部件的大小设置动画。

`size_anim.py`

W
wizardforcel 已提交
31
```py
W
wizardforcel 已提交
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
#!/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_()

```

该示例对`QFrame`小部件的大小进行动画处理。

W
wizardforcel 已提交
90
```py
W
wizardforcel 已提交
91 92 93 94 95 96 97 98
self.button = QPushButton("Start", self)
self.button.clicked.connect(self.doAnim)
self.button.move(30, 30)

```

动画以`QPushButton`开始。

W
wizardforcel 已提交
99
```py
W
wizardforcel 已提交
100 101 102 103 104 105
self.anim = QPropertyAnimation(self.frame, b"geometry")

```

`QPropertyAnimation`已创建。 第一个参数是要动画的目标对象; 在我们的例子中,我们为`QFrame`小部件设置了动画。 第二个参数是将要更改的属性。

W
wizardforcel 已提交
106
```py
W
wizardforcel 已提交
107 108 109 110 111 112
self.anim.setDuration(10000)

```

`setDuration()`设置动画的持续时间(以毫秒为单位)。

W
wizardforcel 已提交
113
```py
W
wizardforcel 已提交
114 115 116 117 118 119 120
self.anim.setStartValue(QRect(150, 30, 100, 100))
self.anim.setEndValue(QRect(150, 30, 200, 200))

```

使用`setStartValue()``setEndValue()`分别定义动画的开始和结束值。

W
wizardforcel 已提交
121
```py
W
wizardforcel 已提交
122 123 124 125 126 127
self.anim.start()

```

动画从`start()`方法开始。

W
wizardforcel 已提交
128
## 使用`QPropertyAnimation`设置颜色的动画
W
wizardforcel 已提交
129 130 131 132 133

下面的示例对小部件的颜色进行动画处理。 由于没有颜色属性,因此我们必须创建一个。

`color_anim.py`

W
wizardforcel 已提交
134
```py
W
wizardforcel 已提交
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
#!/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_()

```

该示例逐渐更改`QLabel`的颜色值。

W
wizardforcel 已提交
213
```py
W
wizardforcel 已提交
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
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`没有颜色属性; 因此,我们用`pyqtProperty`定义一个。 更改此属性将更新标签的颜色。

W
wizardforcel 已提交
231
```py
W
wizardforcel 已提交
232 233 234 235 236 237
self.anim = QPropertyAnimation(self.label, b"color")

```

`QPropertyAnimation`更改标签窗口小部件的`color`属性。

W
wizardforcel 已提交
238
```py
W
wizardforcel 已提交
239 240 241 242 243 244
self.anim.setLoopCount(2)

```

使用`setLoopCount()`方法,我们可以更改动画运行的次数。

W
wizardforcel 已提交
245
```py
W
wizardforcel 已提交
246 247 248 249 250 251 252
self.anim.setStartValue(QColor(0, 0, 0))
self.anim.setEndValue(QColor(255, 255, 255))

```

我们设置开始和结束颜色值。

W
wizardforcel 已提交
253
## 使用`QPropertyAnimation`沿曲线动画
W
wizardforcel 已提交
254 255 256 257 258

以下示例使球沿贝塞尔曲线动画。

`anim_along_curve.py`

W
wizardforcel 已提交
259
```py
W
wizardforcel 已提交
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
#!/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_())

```

该示例在窗口上绘制一条曲线。 沿绘制的曲线为球对象设置动画。

W
wizardforcel 已提交
351
```py
W
wizardforcel 已提交
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
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)

```

球显示在`QLabel`小部件中。

W
wizardforcel 已提交
367
```py
W
wizardforcel 已提交
368 369 370 371 372 373 374 375 376 377
def _set_pos(self, pos):

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

pos = pyqtProperty(QPointF, fset=_set_pos)   

```

我们调整球的位置; 我们希望将标签的中间放置在曲线上。

W
wizardforcel 已提交
378
```py
W
wizardforcel 已提交
379 380 381 382 383 384 385 386
self.path = QPainterPath()
self.path.moveTo(30, 30)
self.path.cubicTo(30, 30, 200, 350, 350, 30) 

```

贝塞尔曲线是用`QPainterPath`创建的。 其`cubicTo()`方法以起点,控制点和终点为参数。

W
wizardforcel 已提交
387
```py
W
wizardforcel 已提交
388 389 390 391 392 393 394 395 396 397 398 399
def paintEvent(self, e):    

    qp = QPainter()
    qp.begin(self)
    qp.setRenderHint(QPainter.Antialiasing)   
    qp.drawPath(self.path)
    qp.end() 

```

`paintEvent()`方法中使用`drawPath()`方法绘制曲线。

W
wizardforcel 已提交
400
```py
W
wizardforcel 已提交
401 402 403 404 405 406
self.anim = QPropertyAnimation(self.ball, b'pos')

```

我们用`QPropertyAnimation`为球的`pos`属性设置动画。

W
wizardforcel 已提交
407
```py
W
wizardforcel 已提交
408 409 410 411
vals = [p/100 for p in range(0, 101)]

```

W
wizardforcel 已提交
412
通过 Python 列表推导式,我们创建了动画步骤列表。 步长是介于 0 和 1 之间的值。
W
wizardforcel 已提交
413

W
wizardforcel 已提交
414
```py
W
wizardforcel 已提交
415 416 417 418 419 420 421 422 423
for i in vals:
    self.anim.setKeyValueAt(i, self.path.pointAtPercent(i))  

```

使用`setKeyValueAt()`,我们定义给定步骤中球的位置。 使用`pointAtPercent()`,我们可以在路径的给定百分比处获得`QPointF`

![Animation along curve](img/a358e8c8049cef4fd02bc3385e49cedb.jpg)

W
wizardforcel 已提交
424
图:沿曲线的动画
W
wizardforcel 已提交
425

W
wizardforcel 已提交
426
## 图形视图框架中的`QPropertyAnimation`
W
wizardforcel 已提交
427 428 429 430 431

`QPropertyAnimation`可以在 Graphics View Framework 中为图形项目设置动画。 动画对象必须继承自`QObject``QGraphicsItem`

`gvf_anim.py`

W
wizardforcel 已提交
432
```py
W
wizardforcel 已提交
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508
#!/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_())

```

该示例在 Graphics View Framework 中使用`QPropertyAnimation`为球对象设置动画。

W
wizardforcel 已提交
509
```py
W
wizardforcel 已提交
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
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 不支持多重继承,我们使用合成技术来满足前面提到的条件。

W
wizardforcel 已提交
526
```py
W
wizardforcel 已提交
527 528 529 530 531 532 533 534 535 536 537
class Example(QGraphicsView):

    def __init__(self):
        super().__init__()

        self.initView()

```

`QGraphicsView`在可滚动视口中可视化`QGraphicsScene`的内容。

W
wizardforcel 已提交
538
```py
W
wizardforcel 已提交
539 540 541 542
self.anim = QPropertyAnimation(self.ball, b'pos')

```

W
wizardforcel 已提交
543
我们将使用`QPropertyAnimation`为球对象的`position`属性设置动画。
W
wizardforcel 已提交
544

W
wizardforcel 已提交
545
```py
W
wizardforcel 已提交
546 547 548 549 550 551
self.anim.setDuration(8000)

```

动画持续八秒钟。

W
wizardforcel 已提交
552
```py
W
wizardforcel 已提交
553 554 555 556 557 558 559 560
self.anim.setKeyValueAt(0.3, QPointF(80, 30))                   
self.anim.setKeyValueAt(0.5, QPointF(200, 30))
self.anim.setKeyValueAt(0.8, QPointF(250, 250))

```

使用`setKeyValueAt()`方法,我们在给定步骤创建具有给定值的关键帧。 换句话说,我们定义了动画给定步骤中球的位置。

W
wizardforcel 已提交
561
```py
W
wizardforcel 已提交
562 563 564 565 566 567
self.scene = QGraphicsScene(self)
self.scene.setSceneRect(0, 0, 300, 300)
self.scene.addItem(self.ball.pixmap_item)

```

W
wizardforcel 已提交
568
创建`QGraphicsScene`并将球添加到场景中。 它提供了一个用于管理大量 2D 图形项目的界面。 注意,我们将`ball`属性添加到场景中,而不是`ball`对象。
W
wizardforcel 已提交
569 570 571

在本教程中,我们使用`QPropertyAnimation`创建了动画。

W
wizardforcel 已提交
572
您可能也对以下相关教程感兴趣: [PyQt5 教程](/gui/pyqt5/)[`QNetworkAccessManager`教程](/pyqt/qnetworkaccessmanager/)[Python 教程](/lang/python/)