class SongInfoCard(QWidget): """ 歌曲信息卡 """ showPlayBarSignal = pyqtSignal() hidePlayBarSignal = pyqtSignal() switchToAlbumInterfaceSig = pyqtSignal(str, str) def __init__(self, parent=None, songInfo: dict = None): super().__init__(parent) self.setSongInfo(songInfo) self.timer = QTimer(self) self.albumCoverLabel = QLabel(self) self.songNameLabel = ClickableLabel(parent=self) self.songerAlbumLabel = ClickableLabel(parent=self) # 初始化标志位 self.isPlayBarVisible = False # 初始化 self.__initWidget() def __initWidget(self): """ 初始化小部件 """ self.resize(1307, 136) self.setFixedHeight(136) self.setMinimumWidth(400) self.albumCoverLabel.move(30, 0) self.albumCoverLabel.setFixedSize(136, 136) self.setAttribute(Qt.WA_TranslucentBackground) self.updateCard(self.songInfo) # 初始化定时器 self.timer.setInterval(3000) self.timer.timeout.connect(self.timerSlot) # 分配ID self.songNameLabel.setObjectName("songNameLabel") self.songerAlbumLabel.setObjectName("songerAlbumLabel") # 设置属性 self.songNameLabel.setProperty("state", "normal") self.songerAlbumLabel.setProperty("state", "normal") # self.__initLayout() self.__setQss() # 安装事件过滤器 self.songNameLabel.installEventFilter(self) self.songerAlbumLabel.installEventFilter(self) # 信号连接到槽 def slot(): return self.switchToAlbumInterfaceSig.emit(self.album, self.songerName) self.songNameLabel.clicked.connect(slot) self.songerAlbumLabel.clicked.connect(slot) def __initLayout(self): """ 初始化布局 """ self.songNameLabel.move(186, 30) self.songerAlbumLabel.move(186, 82) def setSongInfo(self, songInfo: dict): """ 设置歌曲信息 """ self.songInfo = songInfo if not self.songInfo: self.songInfo = {} self.album = self.songInfo.get("album", "未知专辑") self.songName = self.songInfo.get("songName", "未知歌名") self.songerName = self.songInfo.get("songer", "未知歌手") self.albumCoverPath = getCoverPath(self.songInfo.get("modifiedAlbum")) def updateCard(self, songInfo: dict): """ 更新歌曲信息卡 """ self.setSongInfo(songInfo) self.songNameLabel.setText(self.songName) self.songerAlbumLabel.setText(self.songerName + " • " + self.album) self.albumCoverLabel.setPixmap( QPixmap(self.albumCoverPath).scaled( 136, 136, Qt.KeepAspectRatio, Qt.SmoothTransformation ) ) # 调整文本 self.adjustText() def __setQss(self): """ 设置层叠样式 """ with open( r"app\resource\css\playInterfaceSongInfoCard.qss", encoding="utf-8" ) as f: self.setStyleSheet(f.read()) def eventFilter(self, obj, e: QEvent): """ 重写事件过滤器 """ if obj in [self.songNameLabel, self.songerAlbumLabel]: if e.type() == QEvent.MouseButtonPress: self.songNameLabel.setProperty("state", "pressed") self.songerAlbumLabel.setProperty("state", "pressed") self.setStyle(QApplication.style()) elif e.type() == QEvent.Enter: self.songNameLabel.setProperty("state", "hover") self.songerAlbumLabel.setProperty("state", "hover") self.setStyle(QApplication.style()) elif e.type() in [QEvent.MouseButtonRelease, QEvent.Leave]: self.songNameLabel.setProperty("state", "normal") self.songerAlbumLabel.setProperty("state", "normal") self.setStyle(QApplication.style()) return super().eventFilter(obj, e) def enterEvent(self, e): """ 鼠标进入时打开计时器并显示播放栏 """ if not self.isPlayBarVisible: if not self.timer.isActive(): # 显示播放栏 self.timer.start() self.showPlayBarSignal.emit() else: # 重置定时器 self.timer.stop() self.timer.start() def timerSlot(self): """ 定时器溢出时隐藏播放栏 """ self.timer.stop() self.hidePlayBarSignal.emit() def adjustText(self): """ 根据文本长度决定是否插入换行符 """ maxWidth = self.width() - 232 # 设置专辑名歌手名标签的长度 fontMetrics = QFontMetrics(QFont("Microsoft YaHei", 13)) songerAlbumWidth = sum( [fontMetrics.width(i) for i in self.songerAlbumLabel.text()] ) self.songerAlbumLabel.setFixedWidth(min(maxWidth, songerAlbumWidth)) # 调整专辑名标签 fontMetrics = QFontMetrics(QFont("Microsoft YaHei", 21, 75)) newSongName_list = list(self.songName) # type:list totalWidth = 0 isWrap = False for i in range(len(self.songName)): totalWidth += fontMetrics.width(self.songName[i]) if totalWidth > maxWidth: newSongName_list.insert(i, "\n") isWrap = True break if isWrap: self.songNameLabel.setText("".join(newSongName_list)) self.songNameLabel.move(186, 6) self.songerAlbumLabel.move(186, 101) self.songNameLabel.setFixedSize(maxWidth, 83) else: self.songNameLabel.move(186, 26) self.songerAlbumLabel.move(186, 82) self.songNameLabel.setFixedSize(totalWidth, 46) self.songNameLabel.setText(self.songName)
class SongCard(QWidget): """ 歌曲卡 """ clicked = pyqtSignal(int) switchToAlbumInterfaceSig = pyqtSignal(str, str) # 发送专辑名和歌手名 def __init__(self, songInfo: dict, parent=None): super().__init__(parent) self.__getInfo(songInfo) self.__resizeTime = 0 # 记录播放状态 self.isPlaying = False self.__currentState = "leave-notPlay" # 记录下每个小部件所占的最大宽度 self.__maxSongNameCardWidth = 420 self.__maxSongerLabelWidth = 284 self.__maxAlbumLabelWidth = 284 # 记录songCard对应的item的下标 self.itemIndex = None # 创建小部件 self.songNameCard = SongNameCard(songInfo["songName"], self) self.songerLabel = ClickableLabel(songInfo["songer"], self, False) self.albumLabel = ClickableLabel(songInfo["album"], self, False) self.yearLabel = QLabel(songInfo["year"], self) self.durationLabel = QLabel(songInfo["duration"], self) self.buttonGroup = self.songNameCard.buttonGroup self.playButton = self.songNameCard.playButton self.addToButton = self.songNameCard.addToButton self.__label_list = [ self.songerLabel, self.albumLabel, self.yearLabel, self.durationLabel, ] # 创建动画 self.__createAnimations() # 初始化 self.__initWidget() def __initWidget(self): """ 初始化小部件 """ self.__getLabelWidth() self.resize(1234, 60) self.resize(1234, 60) self.setFixedHeight(60) self.albumLabel.setCursor(Qt.PointingHandCursor) self.songerLabel.setCursor(Qt.PointingHandCursor) self.setAttribute(Qt.WA_StyledBackground) # 分配ID和属性 self.setObjectName("songCard") self.albumLabel.setObjectName("clickableLabel") self.songerLabel.setObjectName("clickableLabel") self.setDynamicProperty(self.__currentState) # self.__setQss() # 安装事件过滤器 self.installEventFilter(self) # 信号连接到槽 self.playButton.clicked.connect(lambda: self.clicked.emit(self.itemIndex)) self.albumLabel.clicked.connect( lambda: self.switchToAlbumInterfaceSig.emit( self.albumLabel.text(), self.songerLabel.text() ) ) def __setQss(self): """ 设置层叠样式 """ with open(r"app\resource\css\playInterfaceSongCard.qss", encoding="utf-8") as f: self.setStyleSheet(f.read()) def __getLabelWidth(self): """ 计算标签的长度 """ fontMetrics = QFontMetrics(QFont("Microsoft YaHei", 9)) self.songerWidth = fontMetrics.width(self.songInfo["songer"]) self.albumWidth = fontMetrics.width(self.songInfo["album"]) def setDynamicProperty(self, state: str): """ 设置动态属性,总共4状态,分别为enter-notPlay、enter-play、leave-notPlay、leave-play """ self.yearLabel.setProperty("state", state) self.albumLabel.setProperty("state", state) self.songerLabel.setProperty("state", state) self.durationLabel.setProperty("state", state) if state.endswith("play"): self.isPlaying = True self.songNameCard.setPlay(True) if state.startswith("enter"): self.setProperty("state", "hover") self.songNameCard.buttonGroup.setProperty("state", "hover") else: self.setProperty("state", "leave") self.songNameCard.buttonGroup.setProperty("state", "leave") self.setStyle(QApplication.style()) def resizeEvent(self, e): """ 改变窗口大小时移动标签 """ # super().resizeEvent(e) self.__resizeTime += 1 if self.__resizeTime == 1: self.originalWidth = self.width() width = self.width() - 246 # 计算各个标签所占的最大宽度 self.__maxSongNameCardWidth = int(42 / 99 * width) self.__maxSongerLabelWidth = int((width - self.__maxSongNameCardWidth) / 2) self.__maxAlbumLabelWidth = self.__maxSongerLabelWidth # 如果实际尺寸大于可分配尺寸,就调整大小 self.__adjustWidgetWidth() elif self.__resizeTime > 1: deltaWidth = self.width() - self.originalWidth self.originalWidth = self.width() # 分配多出来的宽度 threeEqualWidth = int(deltaWidth / 3) self.__maxSongNameCardWidth += threeEqualWidth self.__maxSongerLabelWidth += threeEqualWidth self.__maxAlbumLabelWidth += deltaWidth - 2 * threeEqualWidth self.__adjustWidgetWidth() # 移动标签 self.durationLabel.move(self.width() - 45, 20) self.yearLabel.move(self.width() - 190, 20) self.songerLabel.move(self.__maxSongNameCardWidth + 26, 20) self.albumLabel.move(self.songerLabel.x() + self.__maxSongerLabelWidth + 15, 20) # 更新动画目标移动位置 self.__getAniTargetX_list() def eventFilter(self, obj, e: QEvent): """ 更新样式 """ if obj == self: if e.type() in [QEvent.Enter, QEvent.MouseButtonRelease]: self.songNameCard.setWidgetHidden(False) state = "enter-play" if self.isPlaying else "enter-notPlay" self.setDynamicProperty(state) elif e.type() == QEvent.Leave: self.songNameCard.setWidgetHidden(True) state = "leave-play" if self.isPlaying else "leave-notPlay" self.setDynamicProperty(state) elif e.type() == QEvent.MouseButtonPress: self.setProperty("state", "pressed") self.songNameCard.buttonGroup.setProperty("state", "pressed") self.setStyle(QApplication.style()) return super().eventFilter(obj, e) def mousePressEvent(self, e): """ 鼠标按下时移动小部件 """ super().mousePressEvent(e) # 移动小部件 if self.aniGroup.state() == QAbstractAnimation.Stopped: for deltaX, widget in zip(self.__deltaX_list, self.__aniWidget_list): widget.move(widget.x() + deltaX, widget.y()) else: self.aniGroup.stop() for targetX, widget in zip(self.__aniTargetX_list, self.__aniWidget_list): widget.move(targetX, widget.y()) def mouseReleaseEvent(self, e: QMouseEvent): """ 鼠标松开时开始动画 """ for ani, widget, deltaX in zip( self.__ani_list, self.__aniWidget_list, self.__deltaX_list ): ani.setStartValue( QRect(widget.x(), widget.y(), widget.width(), widget.height()) ) ani.setEndValue( QRect(widget.x() - deltaX, widget.y(), widget.width(), widget.height()) ) self.aniGroup.start() # 左键点击时才更新样式 if e.button() == Qt.LeftButton: if not self.isPlaying: self.aniGroup.finished.connect(self.aniFinishedSlot) def aniFinishedSlot(self): """ 动画完成时更新样式 """ # 发送点击信号 self.setDynamicProperty("enter-play") self.songNameCard.checkBox.hide() self.clicked.emit(self.itemIndex) # 动画完成后需要断开连接,为下一次样式更新做准备 self.aniGroup.disconnect() def __adjustWidgetWidth(self): """ 调整小部件宽度 """ # if self.songNameCard.songNameWidth + 41 > self.__maxSongNameCardWidth: self.songNameCard.resize(self.__maxSongNameCardWidth, 60) if self.songerWidth > self.__maxSongerLabelWidth: self.songerLabel.setFixedWidth(self.__maxSongerLabelWidth) else: self.songerLabel.setFixedWidth(self.songerWidth) if self.albumWidth > self.__maxAlbumLabelWidth: self.albumLabel.setFixedWidth(self.__maxAlbumLabelWidth) else: self.albumLabel.setFixedWidth(self.albumWidth) def __createAnimations(self): """ 创建动画 """ self.aniGroup = QParallelAnimationGroup(self) self.__deltaX_list = [13, 5, -3, -11, -13] self.__aniWidget_list = [ self.songNameCard, self.songerLabel, self.albumLabel, self.yearLabel, self.durationLabel, ] self.__ani_list = [ QPropertyAnimation(widget, b"geometry") for widget in self.__aniWidget_list ] for ani in self.__ani_list: ani.setDuration(400) ani.setEasingCurve(QEasingCurve.OutQuad) self.aniGroup.addAnimation(ani) # 记录下移动目标位置 self.__getAniTargetX_list() def __getAniTargetX_list(self): """ 计算动画的初始值 """ self.__aniTargetX_list = [] for deltaX, widget in zip(self.__deltaX_list, self.__aniWidget_list): self.__aniTargetX_list.append(deltaX + widget.x()) def setPlay(self, isPlay: bool): """ 设置歌曲卡的播放状态 """ self.isPlaying = isPlay self.songNameCard.setPlay(isPlay) if isPlay: self.setDynamicProperty("enter-play") else: self.setDynamicProperty("leave-notPlay") def updateSongCard(self, songInfo: dict): """ 更新歌曲卡信息 """ self.resize(self.size()) self.__getInfo(songInfo) self.songNameCard.setSongName(songInfo["songName"]) self.songerLabel.setText(songInfo["songer"]) self.albumLabel.setText(songInfo["album"]) self.yearLabel.setText(songInfo["year"]) self.durationLabel.setText(songInfo["duration"]) # 调整宽度 self.__getLabelWidth() songerWidth = ( self.songerWidth if self.songerWidth <= self.__maxSongerLabelWidth else self.__maxSongerLabelWidth ) albumWidth = ( self.albumWidth if self.albumWidth <= self.__maxAlbumLabelWidth else self.__maxAlbumLabelWidth ) self.songerLabel.setFixedWidth(songerWidth) self.albumLabel.setFixedWidth(albumWidth) def __getInfo(self, songInfo: dict): """ 从歌曲信息中分离信息 """ self.songInfo = songInfo self.year = songInfo["year"] # type:str self.songer = songInfo["songer"] # type:str self.album = songInfo["album"] # type:str self.duration = songInfo["duration"] # type:str self.songName = songInfo["songName"] # type:str