如何在pyqt中实现丝滑滚动字幕

滚动字幕的视觉效果

由于项目需要,所以打算做一个水平的滚动字幕。上网一搜,害。。。又是一群人复制来复制去的,照着他们的方法来还一卡一卡的,因为都是通过计算字符的下标来实现的。最后还是得靠自己。主要思路就是通过定时器定时刷新+绘制两段完整的字符串来达到 蒙蔽双眼 滚动的效果,具体效果如下图所示(录制的帧率为60fps):
如何在pyqt中实现丝滑滚动字幕_第1张图片

具体实现方式

  1. 创建一个窗口ScrollTextWindow,在窗口上显示歌名和歌手名。通过QFontMetrics来计算歌名和歌手名字符串的宽度,选出最大者,再和窗口最大宽度作比较,以设定窗口宽度并决定是否启用滚动效果。在构造函数里面设置了一个spacing属性,用来隔开两段相同的字符串,还设置了两个用于标志位isSongNameAllOutisSongerNameAllOut来标记字符串是否已经全部移出过窗口,这两个标志位很重要,没弄好标志位的话之后会出现字符串位置的跳变。前面说过,实现滚动效果就是靠绘制两段相同的字符串,所以这两段字符串的第一个字符的横坐标就很重要,通过设置的标志位isXXXAllOut、spacing、定时器的溢出次数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 = sum([songFontMetrics.width(i)
                                  for i in self.songName])
        songerFontMetrics = QFontMetrics(QFont('Microsoft YaHei', 12, 500))
        self.songerNameWidth = sum(
            [songerFontMetrics.width(i) for i in 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)

  1. 接着创建父级窗口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学习日记)