平滑滚动的视觉效果

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. rsync 守护进程及实时同步

    目录 rsync 守护进程及实时同步 rsync简介 rsync特性 rsync应用场景 cp命令 scp命令 rsync的传输方式 rsync的传输模式 rsync实际使用 rsync命令 案例 r ...

  2. Second Order Optimization for Adversarial Robustness and Interpretability

    目录 概 主要内容 (4)式的求解 超参数 Tsiligkaridis T., Roberts J. Second Order Optimization for Adversarial Robustn ...

  3. PL2586旺玖|USB 2.0HUB 工业级芯片|PROLIFIC PL2586

    工业级  USB 2.0 HUB 高速4端口集线器控制器 PL2586 1.PL2586说明   PL2586是USB 2.0高速4端口集线器控制器的高性能解决方案,完全符合通用串行总线规范2.0.控 ...

  4. Java Web程序设计笔记 • 【第8章 会话跟踪技术进阶】

    全部章节   >>>> 本章目录 8.1 Session机制 8.1.1 Session 简介 8.1.2 创建 HttpSession 实例 8.1.3 HttpSesiso ...

  5. Sqoop2开启Kerberos安全模式

    Sqoop2开启Kerberos安全模式, 基于版本sqoop-1.99.7, 在已经安装好的sqoop2环境上配置kerberos. 1.安装规划 10.43.159.9 zdh-9 sqoop2k ...

  6. centos6.5搭建Apache-虚拟主机

    一.配置基于域名的虚拟用户 1.创建虚拟用户的网页根目录 cd /usr/local/httpd/htdocs/ mkdir benetcom cd benetcom echo "<h ...

  7. PowerShell【变量篇】

    PS C:\Users\Administrator> $str='这是一个变量' PS C:\Users\Administrator> $str 这是一个变量 PS C:\Users\Ad ...

  8. js 关于replace() 的使用心得

    1.前言 我想把一段话 let a = "抱歉,您当前的主治医生有紧急情况不得不下班,您的预约将由<br>医生:里斯<br>为您就诊,<br>诊室位置:门 ...

  9. HarmonyOS新能力让数据多端协同更便捷,数据跨端迁移更高效!

    作者:yijian,终端OS分布式文件系统专家:gongashi,终端OS分布式数据管理专家 HarmonyOS作为分布式操作系统,其分布式数据管理能力非常重要.我们也一直围绕持续为开发者带来全局&q ...

  10. 阿里云服务器ECS Ubuntu16.04 + Seafile 搭建私人网盘 (Seafile Pro)

    原文链接:? 传送门 本文主要讲述 使用 Ubuntu 16.04 云服务器 通过脚本实现对 Seafile Pro 的安装,完成私人网盘的搭建 首先给出 Seafile 专业版的下载地址(Linux ...