如何在 pyqt 中使用动画实现平滑滚动的 QScrollArea
前言
在之前的博客《如何在 pyqt 中实现平滑滚动的 QScrollArea》中,我们使用定时器和队列实现了平滑滚动。但是实现代码还是有一点复杂,所以这篇博客将使用 Qt 的动画框架 QPropertyAnimation
来实现相同的功能。
实现过程
SmoothScrollBar
滚动过程其实就是改变 QScrollBar
的 value()
的过程,Qt 自带的 QScrollArea
之所以无法平滑滚动,就是因为滚动时在 QScrollBar
的两个 value()
之间进行跳变。如果我们能在两个滚动值之间进行插值,就能实现平滑滚动了,这里通过重写 setValue()
函数来启动滚动动画。
class SmoothScrollBar(QScrollBar):
""" Smooth scroll bar """
scrollFinished = pyqtSignal()
def __init__(self, parent=None):
QScrollBar.__init__(self, parent)
self.ani = QPropertyAnimation()
self.ani.setTargetObject(self)
self.ani.setPropertyName(b"value")
self.ani.setEasingCurve(QEasingCurve.OutCubic)
self.ani.setDuration(500)
self.__value = self.value()
self.ani.finished.connect(self.scrollFinished)
def setValue(self, value: int):
if value == self.value():
return
# stop running animation
self.ani.stop()
self.scrollFinished.emit()
self.ani.setStartValue(self.value())
self.ani.setEndValue(value)
self.ani.start()
def scrollValue(self, value: int):
""" scroll the specified distance """
self.__value += value
self.__value = max(self.minimum(), self.__value)
self.__value = min(self.maximum(), self.__value)
self.setValue(self.__value)
def scrollTo(self, value: int):
""" scroll to the specified position """
self.__value = value
self.__value = max(self.minimum(), self.__value)
self.__value = min(self.maximum(), self.__value)
self.setValue(self.__value)
def resetValue(self, value):
self.__value = value
def mousePressEvent(self, e):
self.ani.stop()
super().mousePressEvent(e)
self.__value = self.value()
def mouseReleaseEvent(self, e):
self.ani.stop()
super().mouseReleaseEvent(e)
self.__value = self.value()
def mouseMoveEvent(self, e):
self.ani.stop()
super().mouseMoveEvent(e)
self.__value = self.value()
SmoothScrollArea
最后需要将 QScrollArea
的默认滚动条替换为平滑滚动的 SmoothScrollBar
:
class SmoothScrollArea(QScrollArea):
""" Smooth scroll area """
def __init__(self, parent=None):
super().__init__(parent)
self.hScrollBar = SmoothScrollBar()
self.vScrollBar = SmoothScrollBar()
self.hScrollBar.setOrientation(Qt.Horizontal)
self.vScrollBar.setOrientation(Qt.Vertical)
self.setVerticalScrollBar(self.vScrollBar)
self.setHorizontalScrollBar(self.hScrollBar)
def setScrollAnimation(self, orient, duration, easing=QEasingCurve.OutCubic):
""" set scroll animation
Parameters
----------
orient: Orient
scroll orientation
duration: int
scroll duration
easing: QEasingCurve
animation type
"""
bar = self.hScrollBar if orient == Qt.Horizontal else self.vScrollBar
bar.ani.setDuration(duration)
bar.ani.setEasingCurve(easing)
def wheelEvent(self, e):
if e.modifiers() == Qt.NoModifier:
self.vScrollBar.scrollValue(-e.angleDelta().y())
else:
self.hScrollBar.scrollValue(-e.angleDelta().x())
测试
下面是一个简单的图片查看器测试程序:
# coding:utf-8
import sys
from PyQt5.QtCore import QEasingCurve, Qt
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QApplication, QLabel
class Demo(SmoothScrollArea):
def __init__(self):
super().__init__()
self.label = QLabel(self)
self.label.setPixmap(QPixmap("shoko.jpg"))
# customize scroll animation
self.setScrollAnimation(Qt.Vertical, 400, QEasingCurve.OutQuint)
self.setScrollAnimation(Qt.Horizontal, 400, QEasingCurve.OutQuint)
self.horizontalScrollBar().setValue(1900)
self.setWidget(self.label)
self.resize(1200, 800)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Demo()
w.show()
app.exec_()
测试用的图片如下(硝子酱真可爱🥰:
写在最后
至此平滑滚动的实现方式就已介绍完毕了,更多自定义小部件可以参见 PyQt-Fluent-Widgets,以上~~