滚动字幕的视觉效果

网上有很多博客介绍了滚动字幕的实现方法,懂得都懂,大部是 Ctrl C + Ctrl V,效果还很差,最后还是得靠自己。主要思路就是通过定时器定时刷新+绘制两段完整的字符串来达到 蒙蔽双眼 滚动的效果,具体效果如下图所示

具体实现方式

ScrollTextWindow 类

窗口 ScrollTextWindow 上显示歌名和歌手名。通过 QFontMetrics 来计算歌名和歌手名字符串的宽度,选出最大者,再和窗口最大宽度作比较,以设定窗口宽度并决定是否启用滚动效果。在构造函数里面设置了一个 spacing 属性,用来隔开两段相同的字符串,还设置了两个用于标志位 isSongNameAllOutisSongerNameAllOut 来标记字符串是否已经全部移出过窗口,这两个标志位很重要,没弄好标志位的话之后会出现字符串位置的跳变。前面说过,实现滚动效果就是靠绘制两段相同的字符串,所以这两段字符串的第一个字符的横坐标就很重要,通过设置的标志位isXXXAllOutspacing、定时器的溢出次数 currentIndex 以及字符串的移动步长 moveStep 来决定这两个字符串的的横坐标,具体代码如下:

import sys

from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QFont, QFontMetrics, QPainter,QPixmap
from PyQt5.QtWidgets import QApplication, QLabel, QWidget class ScrollTextWindow(QWidget):
""" 滚动字幕 """ def __init__(self, songName, songerName, parent=None):
super().__init__(parent)
self.songName = songName
self.songerName = songerName
# 实例化定时器
self.timer = QTimer(self)
# 设置刷新时间和移动距离
self.timeStep = 20
self.moveStep = 1
self.songCurrentIndex = 0
self.songerCurrentIndex = 0
# 设置字符串溢出标志位
self.isSongNameAllOut = False
self.isSongerNameAllOut = False
# 设置两段字符串之间留白的宽度
self.spacing = 25
# 初始化界面
self.initWidget() def initWidget(self):
""" 初始化界面 """
self.setFixedHeight(115)
self.setAttribute(Qt.WA_StyledBackground)
# 调整窗口宽度
self.adjustWindowWidth()
# 初始化定时器
self.timer.setInterval(self.timeStep)
self.timer.timeout.connect(self.updateIndex)
# 只要有一个字符串宽度大于窗口宽度就开启滚动:
if self.isSongerNameTooLong or self.isSongNameTooLong:
self.timer.start() def getTextWidth(self):
""" 计算文本的总宽度 """
songFontMetrics = QFontMetrics(QFont('Microsoft YaHei', 14, 400))
self.songNameWidth = songFontMetrics.width(self.songName)
songerFontMetrics = QFontMetrics(QFont('Microsoft YaHei', 12, 500))
self.songerNameWidth = songerFontMetrics.width(self.songerName) def adjustWindowWidth(self):
""" 根据字符串长度调整窗口宽度 """
self.getTextWidth()
maxWidth = max(self.songNameWidth, self.songerNameWidth)
# 判断是否有字符串宽度超过窗口的最大宽度
self.isSongNameTooLong = self.songNameWidth > 250
self.isSongerNameTooLong = self.songerNameWidth > 250
# 设置窗口的宽度
self.setFixedWidth(min(maxWidth, 250)) def updateIndex(self):
""" 更新下标 """
self.update()
self.songCurrentIndex += 1
self.songerCurrentIndex += 1
# 设置下标重置条件
resetSongIndexCond = self.songCurrentIndex * \
self.moveStep >= self.songNameWidth + self.spacing * self.isSongNameAllOut
resetSongerIndexCond = self.songerCurrentIndex * \
self.moveStep >= self.songerNameWidth + self.spacing * self.isSongerNameAllOut
# 只要条件满足就要重置下标并将字符串溢出置位,保证在字符串溢出后不会因为留出的空白而发生跳变
if resetSongIndexCond:
self.songCurrentIndex = 0
self.isSongNameAllOut = True
if resetSongerIndexCond:
self.songerCurrentIndex = 0
self.isSongerNameAllOut = True def paintEvent(self, e):
""" 绘制文本 """
# super().paintEvent(e)
painter = QPainter(self)
painter.setPen(Qt.white)
# 绘制歌名
painter.setFont(QFont('Microsoft YaHei', 14))
if self.isSongNameTooLong:
# 实际上绘制了两段完整的字符串
# 从负的横坐标开始绘制第一段字符串
painter.drawText(self.spacing * self.isSongNameAllOut - self.moveStep *
self.songCurrentIndex, 54, self.songName)
# 绘制第二段字符串
painter.drawText(self.songNameWidth - self.moveStep * self.songCurrentIndex +
self.spacing * (1 + self.isSongNameAllOut), 54, self.songName)
else:
painter.drawText(0, 54, self.songName) # 绘制歌手名
painter.setFont(QFont('Microsoft YaHei', 12, 500))
if self.isSongerNameTooLong:
painter.drawText(self.spacing * self.isSongerNameAllOut - self.moveStep *
self.songerCurrentIndex, 82, self.songerName)
painter.drawText(self.songerNameWidth - self.moveStep * self.songerCurrentIndex +
self.spacing * (1 + self.isSongerNameAllOut), 82, self.songerName)
else:
painter.drawText(0, 82, self.songerName)

SongInfoCard 类

父级窗口 SongInfoCard 用来显示专辑封面和滚动字幕,代码如下:

class SongInfoCard(QWidget):
""" 播放栏左侧歌曲信息卡 """ def __init__(self, songInfo: dict, parent=None):
super().__init__(parent)
# 保存信息
self.songInfo = songInfo
self.songName = self.songInfo['songName']
self.songerName = self.songInfo['songer']
# 实例化小部件
self.albumPic = QLabel(self)
self.scrollTextWindow = ScrollTextWindow(
self.songName, self.songerName, self)
# 初始化界面
self.initWidget() def initWidget(self):
""" 初始化小部件 """
self.setFixedHeight(115)
self.setFixedWidth(115 + 15 + self.scrollTextWindow.width() + 25)
self.setAttribute(Qt.WA_StyledBackground)
self.setWindowFlags(Qt.FramelessWindowHint)
self.scrollTextWindow.move(130, 0)
self.albumPic.setPixmap(QPixmap(self.songInfo['album'][-1]).scaled(
115, 115, Qt.KeepAspectRatio, Qt.SmoothTransformation)) if __name__ == "__main__":
app = QApplication(sys.argv)
songInfo = {
'songName': 'ハッピーでバッドな眠りは浅い', 'songer': '鎖那',
'album': [r'resource\Album Cover\ハッピーでバッドな眠りは浅い\ハッピーでバッドな眠りは浅い.png']}
demo = SongInfoCard(songInfo)
demo.setStyleSheet('background:rgb(129,133,137)')
demo.show()
sys.exit(app.exec_())

结语

至此,完美实现了丝滑的滚动效果,对你有帮助的话就点个赞吧٩(๑>◡<๑)۶ 。

如何在pyqt中实现丝滑滚动字幕的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  9. 如何在pyqt中使用 QGraphicsView 实现图片查看器

    前言 在 PyQt 中可以使用很多方式实现照片查看器,最朴素的做法就是重写 QWidget 的 paintEvent().mouseMoveEvent 等事件,但是如果要在图像上多添加一些形状,那么在 ...

随机推荐

  1. 【LeetCode】143. Reorder List 解题报告(Python)

    [LeetCode]143. Reorder List 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://f ...

  2. 【LeetCode】468. Validate IP Address 解题报告(Python)

    [LeetCode]468. Validate IP Address 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个人博客: h ...

  3. Docker 与 K8S学习笔记(五)—— 容器的操作(上篇)

    上一篇我们介绍了Dockerfile的基本编写方法,这一节我们来看看Docker容器的常用操作. 一.容器的运行方式 容器有两种运行方式,即daemon形式运行与非daemon形式运行,通俗地讲就是长 ...

  4. Dubbo为什么要用Go重写?

    先说两句 我常常在散步时思考很多技术上的「为什么问题」,有时一个问题会想很久,直到问题的每一个点都能说服自己时,才算完结.于是想把这些思考记录下来,形成文章,可以当做一个新的系列.这些文章中你可能看不 ...

  5. 4.1.4 统计“锦途网”旅游线路平均价格,并采用尽可能多的方式将该价格赋给用户会话变量 @avg_short_price,并输出该变量

    查看本章节 查看作业目录 需求说明: 统计"锦途网"旅游线路平均价格,并采用尽可能多的方式将该价格赋给用户会话变量 @avg_short_price,并输出该变量 在 MySQL ...

  6. HTML网页设计中 link 和 @import 的区别

    页面中使用CSS的方式主要有3种:行内添加定义style属性值,页面头部内嵌调用和外面链接调用,其中外面引用有两种:link和@import.外部引用CSS两种方式link和@import的方式分别是 ...

  7. MySQL数据操作与查询笔记 • 【第6章 聚合函数和分组查询】

    全部章节   >>>> 本章目录 6.1 sum.max 和 min 聚合函数 6.1.1 聚合函数介绍 6.1.2 sum 函数 6.1.3 max/min 函数 6.2 a ...

  8. 过年有燃放烟花爆竹禁令那我们用css写一个仙女棒烟花看看吧

    先是去找了一张简易画的烟花照片,可以看出主要结构为歪曲的线条结构. 方案一: 弯曲的线条第一反应到的就是"圆角边框": width: 200px; height: 200px; b ...

  9. SpringBoot 之 Dao层模拟数据库操作

    单表操作: # src/main/java/com/wu/dao/DepartmentDao .java @Repository public class DepartmentDao { privat ...

  10. spring-aop(二)学习笔记

    常用增强处理类型 增强处理类型                                                        特点 before 前置增强处理,在目标方法前织入增强处理 ...