原文鏈接: https://blog.51cto.com/u_15127644/2756688
滾動字幕的視覺效果
網上有很多博客介紹了滾動字幕的實現方法,懂得都懂,大部是 Ctrl C
+ Ctrl V
,效果還很差,最后還是得靠自己。主要思路就是通過定時器定時刷新+繪制兩段完整的字符串來達到 蒙蔽雙眼 滾動的效果,具體效果如下圖所示

具體實現方式
ScrollTextWindow 類
窗口 ScrollTextWindow
上顯示歌名和歌手名。通過 QFontMetrics
來計算歌名和歌手名字符串的寬度,選出最大者,再和窗口最大寬度作比較,以設定窗口寬度並決定是否啟用滾動效果。在構造函數里面設置了一個 spacing
屬性,用來隔開兩段相同的字符串,還設置了兩個用於標志位 isSongNameAllOut
和 isSongerNameAllOut
來標記字符串是否已經全部移出過窗口,這兩個標志位很重要,沒弄好標志位的話之后會出現字符串位置的跳變。前面說過,實現滾動效果就是靠繪制兩段相同的字符串,所以這兩段字符串的第一個字符的橫坐標就很重要,通過設置的標志位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 = 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_())