平滑滚动的视觉效果

Qt 自带的 QScrollArea 滚动时只能在两个像素节点之间跳变,看起来很突兀。刚开始试着用 QPropertyAnimation 来实现平滑滚动,但是效果不太理想。所以直接开了定时器,重写 wheelEvent() 来实现平滑滚动。效果如下:

实现思路

定时器溢出是需要时间的,无法立马处理完所有的滚轮事件,所以自己复制一个滚轮事件 lastWheelEvent,然后计算每一次滚动需要移动的距离和步数,将这两个参数绑定在一起放入队列中。定时器溢出时就将所有未处理完的事件对应的距离累加得到 totalDelta,每个未处理事件的步数-1,将 totalDeltalastWheelEvent 作为参数传入 QWheelEvent的构造函数,构建出真正需要的滚轮事件 e 并将其发送到 app 的事件处理队列中,发生滚动。

具体代码

import sys
from collections import deque
from enum import Enum
from math import cos, pi from PyQt5.QtCore import QDateTime, Qt, QTimer, QPoint
from PyQt5.QtGui import QWheelEvent
from PyQt5.QtWidgets import QApplication, QScrollArea class ScrollArea(QScrollArea):
""" 一个可以平滑滚动的区域 """ def __init__(self, parent=None):
super().__init__(parent)
self.fps = 60
self.duration = 400
self.stepsTotal = 0
self.stepRatio = 1.5
self.acceleration = 1
self.lastWheelEvent = None
self.scrollStamps = deque()
self.stepsLeftQueue = deque()
self.smoothMoveTimer = QTimer(self)
self.smoothMode = SmoothMode(SmoothMode.COSINE)
self.smoothMoveTimer.timeout.connect(self.smoothMove)
self.setVerticalScrollMode(self.ScrollPerPixel) def wheelEvent(self, e: QWheelEvent):
""" 实现平滑滚动效果 """
if self.smoothMode == SmoothMode.NO_SMOOTH:
super().wheelEvent(e)
return
# 将当前时间点插入队尾
now = QDateTime.currentDateTime().toMSecsSinceEpoch()
self.scrollStamps.append(now)
while now - self.scrollStamps[0] > 500:
self.scrollStamps.popleft()
# 根据未处理完的事件调整移动速率增益
accerationRatio = min(len(self.scrollStamps) / 15, 1)
if not self.lastWheelEvent:
self.lastWheelEvent = QWheelEvent(e)
else:
self.lastWheelEvent = e
# 计算步数
self.stepsTotal = self.fps * self.duration / 1000
# 计算每一个事件对应的移动距离
delta = e.angleDelta().y() * self.stepRatio
if self.acceleration > 0:
delta += delta * self.acceleration * accerationRatio
# 将移动距离和步数组成列表,插入队列等待处理
self.stepsLeftQueue.append([delta, self.stepsTotal])
# 定时器的溢出时间t=1000ms/帧数
self.smoothMoveTimer.start(1000 / self.fps) def smoothMove(self):
""" 计时器溢出时进行平滑滚动 """
totalDelta = 0
# 计算所有未处理完事件的滚动距离,定时器每溢出一次就将步数-1
for i in self.stepsLeftQueue:
totalDelta += self.subDelta(i[0], i[1])
i[1] -= 1
# 如果事件已处理完,就将其移出队列
while self.stepsLeftQueue and self.stepsLeftQueue[0][1] == 0:
self.stepsLeftQueue.popleft()
# 构造滚轮事件
e = QWheelEvent(self.lastWheelEvent.pos(),
self.lastWheelEvent.globalPos(),
QPoint(),
QPoint(0, totalDelta),
round(totalDelta),
Qt.Vertical,
self.lastWheelEvent.buttons(),
Qt.NoModifier)
# 将构造出来的滚轮事件发送给app处理
QApplication.sendEvent(self.verticalScrollBar(), e)
# 如果队列已空,停止滚动
if not self.stepsLeftQueue:
self.smoothMoveTimer.stop() def subDelta(self, delta, stepsLeft):
""" 计算每一步的插值 """
m = self.stepsTotal / 2
x = abs(self.stepsTotal - stepsLeft - m)
# 根据滚动模式计算插值
res = 0
if self.smoothMode == SmoothMode.NO_SMOOTH:
res = 0
elif self.smoothMode == SmoothMode.CONSTANT:
res = delta / self.stepsTotal
elif self.smoothMode == SmoothMode.LINEAR:
res = 2 * delta / self.stepsTotal * (m - x) / m
elif self.smoothMode == SmoothMode.QUADRATI:
res = 3 / 4 / m * (1 - x * x / m / m) * delta
elif self.smoothMode == SmoothMode.COSINE:
res = (cos(x * pi / m) + 1) / (2 * m) * delta
return res class SmoothMode(Enum):
""" 滚动模式 """
NO_SMOOTH = 0
CONSTANT = 1
LINEAR = 2
QUADRATI = 3
COSINE = 4

写在最后

也许有人会发现动图的界面和 Groove音乐 很像,实现代码放在了github。如果这篇博客或者仓库中的代码对你有启发的话就点个赞吧٩(๑>◡<๑)۶

如何在pyqt中实现平滑滚动的QScrollArea的更多相关文章

  1. 页面中的平滑滚动——smooth-scroll.js的使用

    正常的本页面锚链接跳转的时候跟PPT似的,特别生硬,用户体验非常差. 这时候我们就可以借助smooth-scroll.js这个插件,来实现本页面的平滑的跳转. 1首先,导入必须的JS文件 <sc ...

  2. 如何在pyqt中自定义无边框窗口

    前言 之前写过很多关于无边框窗口并给窗口添加特效的博客,按照时间线罗列如下: 如何在pyqt中实现窗口磨砂效果 如何在pyqt中实现win10亚克力效果 如何在pyqt中通过调用SetWindowCo ...

  3. 如何在pyqt中实现窗口磨砂效果

    磨砂效果的实现思路 这两周一直在思考怎么在pyqt上实现窗口磨砂效果,网上搜了一圈,全都是 C++ 的实现方法.正好今天查python的官方文档的时候看到了 ctypes 里面的 HWND,想想倒不如 ...

  4. 如何在pyqt中实现win10亚克力效果

    亚克力效果的实现思路 上一篇博客<如何在pyqt中实现窗口磨砂效果> 中实现了win7中的Aero效果,但是和win10的亚克力效果相比,Aero还是差了点内味.所以今天早上又在网上搜了一 ...

  5. 如何在pyqt中通过调用 SetWindowCompositionAttribute 实现Win10亚克力效果

    亚克力效果 在<如何在pyqt中实现窗口磨砂效果>和<如何在pyqt中实现win10亚克力效果>中,我们调用C++ dll来实现窗口效果,这种方法要求电脑上必须装有MSVC.V ...

  6. 如何在pyqt中在实现无边框窗口的同时保留Windows窗口动画效果(一)

    无边框窗体的实现思路 在pyqt中只要 self.setWindowFlags(Qt.FramelessWindowHint) 就可以实现边框的去除,但是没了标题栏也意味着窗口大小无法改变.窗口无法拖 ...

  7. 如何在pyqt中给无边框窗口添加DWM环绕阴影

    前言 在之前的博客<如何在pyqt中通过调用SetWindowCompositionAttribute实现Win10亚克力效果>中,我们实现了窗口的亚克力效果,同时也用SetWindowC ...

  8. 如何在pyqt中实现带动画的动态QMenu

    弹出菜单的视觉效果 QLineEdit 原生的菜单弹出效果十分生硬,而且样式很丑.所以照着Groove中单行输入框弹出菜单的样式和动画效果写了一个可以实现动态变化Item的弹出菜单,根据剪贴板的内容是 ...

  9. 如何在 pyqt 中捕获并处理 Alt+F4 快捷键

    前言 如果在 Windows 系统的任意一个窗口中按下 Alt+F4,默认行为是关闭窗口(或者最小化到托盘).对于使用了亚克力效果的窗口,使用 Alt+F4 最小化到托盘,再次弹出窗口的时候可能出现亚 ...

随机推荐

  1. Myeclipse查看jdk源代码

    过程如下: 1.点 "window"-> "Preferences" -> "Java" -> "Install ...

  2. Spring事务的基本原理

    Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的.. 对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行: 获取连接 Connecti ...

  3. One Pixel Attack for Fooling Deep Neural Networks

    目录 概 主要内容 问题描述 Differential Evolution (DE) 实验 Su J, Vargas D V, Sakurai K, et al. One Pixel Attack f ...

  4. IT6516替代方案|CS5212替代IT6516|CapstoneCS5212

    IT6516/IT6516BFN:是一款DP显示端口转VGA转换器的嵌入式MCU单片机. IT6516/IT6516BFN结合DisplayPort接收器和三重DAC,通过转换功能支持DisplayP ...

  5. 使用.NET 6开发TodoList应用(15)——实现查询搜索

    系列导航及源代码 使用.NET 6开发TodoList应用文章索引 需求 本文我们继续来看查询过程中的另外一个需求:搜索.搜索的含义是目标字段的全部或者部分值匹配请求中的搜索条件,对应到数据库层面是C ...

  6. Clover支持目录多标签页

    1.简介 Clover是Windows Explorer资源管理器的一个扩展, 为其增加类似谷歌 Chrome 浏览器的多标签页功能. 2.推荐用法 下面是我使用的Clover的截图: 可以看到同时打 ...

  7. js 中&&的使用

    遇到下列代码goHome && (await router.replace(PageEnum.BASE_HOME)); &&这个逻辑与的作用是,当goHome为true ...

  8. 如何在 CentOS 上安装 dos2unix 和 unix2dos 命令

    yum install -y dos2unix 注意:以上安装包既包含 dos2unix 命令,又包含 unix2dos 命令.

  9. 第10组 Beta冲刺 (2/5)(组长)

    1.1基本情况 ·队名:今晚不睡觉 ·组长博客:https://www.cnblogs.com/cpandbb/p/14015412.html ·作业博客:https://edu.cnblogs.co ...

  10. 实验 5 :OpenFlow 协议分析和 OpenDaylight 安装

    实验 5 :OpenFlow 协议分析和 OpenDaylight 安装 一.实验目的 回顾 JDK 安装配置,了解 OpenDaylight 控制的安装,以及 Mininet 如何连接: 通过抓包获 ...