文章目录
- 一.前言
- 二.设计
- 1.思路
- 2.布局
- 3.自定义组件
- 1.支持hover事件的QLabel
- 2.自定义的QToolButton
- 4.图片圆角
- 5.动画效果
- 三.源代码
- 四.总结
一.前言
有网友给我留言,问我那种可交互的音乐组件是怎么制作的,交互效果是如何实现的,本篇以QQ音乐的一个组件为例,从布局到动画,详细阐述相关组件的制作流程,实现下图效果:
看完本篇,你将会收获:
1.组件布局
2.信号与槽
3.自定义组件
4.图片圆角
5.动画效果
6.组件叠放
Let’s do it !
二.设计
1.思路
这里讲一下我们设计的主要思路:
此组件主体为垂直布局,由上方封面以及下方文字组成,其中在默认状态下,封面右下角展示歌单播放量,背景为黑色,文字为白色,当鼠标移入封面时,右下角展示的信息消失,在封面正中央展示一个播放按钮,背景颜色为黑色半透明,当鼠标移入按钮时,按钮变成绿色。
2.布局
我们使用一个QLabel作为封面图片的载体,通过调用.setPixmap()方法把图片设置到QLabel中,封面右下角的播放数据也使用一个QLabel,通过QSS的background-color改变背景颜色、border-radius设置圆角,padding等参数设置边距,再实例化一个QLabel作为鼠标移入后按钮的背景,起到一个遮罩层的效果,最后再使用一个QToolButton,通过设置它的ICON设置其图表。难点来了,如何在封面上叠放歌单数据、遮罩层背景以及按钮。这里我们实例化一个QVBoxLayout,把它的父类设置为封面QLabel,之后把需要叠放的组件加入到布局中即可,通过创建QPropertyAnimation控制布局内的组件显示与隐藏,即可实现我们所说的交互。
相关代码参考:
def ui_init(self):
self.layout = QVBoxLayout()
self.cover_label = MyLabel(self)
self.cover_label.setObjectName("cover_label")
self.name_label = QLabel()
self.name_label.setWordWrap(False)
self.name_label.setCursor(Qt.PointingHandCursor)
self.name_label.setObjectName("name_label")
self.hover_btn_label = QLabel()
self.hover_btn_label.setObjectName("hover_btn_label")
self.hover_btn = Hover_mormal_button(self.hover_btn_label)
self.hover_btn.setObjectName("hover_btn")
self.hover_btn.setIconSize(QSize(55, 55))
self.hover_btn.set_icon("fa.play-circle")
self.hover_btn.setVisible(False)
self.hover_btn_label.setVisible(False)
top_hover_layout = QVBoxLayout(self.cover_label)
self.hover_label = QLabel()
self.hover_label.setObjectName("hover_label")
self.hover_label.setFixedSize(90, 34)
top_hover_layout.addWidget(self.hover_btn_label)
top_hover_layout.setSpacing(0)
top_hover_layout.setContentsMargins(0, 0, 0, 0)
hover_layout = QVBoxLayout(self.hover_btn_label)
hover_layout.addWidget(self.hover_btn)
hover_layout.setAlignment(self.hover_btn, Qt.AlignCenter | Qt.AlignCenter)
top_hover_layout.addWidget(self.hover_label)
top_hover_layout.setAlignment(self.hover_label, Qt.AlignRight | Qt.AlignBottom)
self.hover_label.setText("🎧34.6万")
self.name_label.setText("抖音热歌:超好听的店铺BGM")
self.cover_label.setPixmap(
rounded_pixmap(QPixmap(":res/rescourses/cover.png").scaled(167, 167), radius=10))
self.layout.addWidget(self.cover_label)
self.layout.addWidget(self.name_label)
self.layout.setStretch(0, 10)
self.setLayout(self.layout)
3.自定义组件
为了代码复用,这里涉及两个自定义组件,分别实现了不同的功能,下面我来介绍一下。
1.支持hover事件的QLabel
这里重写了QLabel的enterEvent与leaveEvent事件,通过检测鼠标的移入和移出事件,将hovered信号发射出去,发射给父窗口。
class MyLabel(QLabel):
clicked = pyqtSignal() # 能够点击
hovered = pyqtSignal(bool)
def __init__(self, parent=None):
super(MyLabel, self).__init__(parent)
self.setParent(parent)
self.setScaledContents(True) # 实现缩放
def mousePressEvent(self, ev: QtGui.QMouseEvent):
self.clicked.emit()
def enterEvent(self, a0: QtCore.QEvent):
self.setCursor(Qt.PointingHandCursor) # 改变鼠标样式
self.hovered.emit(True)
def leaveEvent(self, a0: QtCore.QEvent):
self.hovered.emit(False)
2.自定义的QToolButton
这是笔者自定义的一个QToolButton,因为要设计UI统一,并且要做到代码复用,这里干脆直接封装成一个类,对外提供一个接口,可设置鼠标hover之后的图标颜色样式,默认白色,hover之后是绿色。
class Hover_mormal_button(QToolButton):
def __init__(self, parent=None):
super(Hover_mormal_button, self).__init__()
self.setParent(parent)
self.normal_color = "white"
self.hovered_color = "#1fcf9e"
self.icon_name = ""
self.setCursor(Qt.PointingHandCursor)
self.setIconSize(QSize(20, 20))
def set_icon(self, icon_name, normal_color="", hovered_color=""):
self.icon_name = icon_name
if normal_color:
self.normal_color = normal_color
if hovered_color:
self.hovered_color = hovered_color
self.setIcon(QIcon(qtawesome.icon(self.icon_name, color=self.normal_color)))
def enterEvent(self, a0: QtCore.QEvent):
self.setIcon(QIcon(qtawesome.icon(self.icon_name, color=self.hovered_color)))
super(Hover_mormal_button, self).enterEvent(a0)
def leaveEvent(self, a0: QtCore.QEvent):
self.setIcon(QIcon(qtawesome.icon(self.icon_name, color=self.normal_color)))
4.图片圆角
这里使用一个公共方法将QPixmap的边缘做圆角处理。
def rounded_pixmap(pixmap, radius):
rounded_pixmap = QPixmap(pixmap.size())
rounded_pixmap.fill(Qt.transparent)
painter = QPainter(rounded_pixmap)
painter.setRenderHint(QPainter.Antialiasing)
path = QPainterPath()
path.addRoundedRect(0, 0, pixmap.width(), pixmap.height(), radius, radius)
painter.setClipPath(path)
painter.drawPixmap(0, 0, pixmap)
return rounded_pixmap
5.动画效果
前面说了,是通过鼠标移入移出控制封面上方信息与遮罩层按钮的隐藏与显示实现的交互,绑定的代码如下:
self.cover_label.hovered.connect(lambda s: self.control_hover_layer(s))
槽函数代码如下
def control_hover_layer(self, s):
"""
控制遮罩层
:return:
"""
self.hover_label.setVisible(not s)
effect = QGraphicsOpacityEffect(self)
self.hover_btn_label.setGraphicsEffect(effect)
animation = QPropertyAnimation(self)
animation.setTargetObject(effect)
animation.setPropertyName(b'opacity')
animation.setDuration(300)
animation.setKeyValueAt(0, 0)
animation.setKeyValueAt(1, 1)
if s:
animation.setDirection(QAbstractAnimation.Forward)
else:
animation.setDirection(QAbstractAnimation.Backward)
animation.start(QAbstractAnimation.DeleteWhenStopped)
self.hover_btn.setVisible(s)
self.hover_btn_label.setVisible(s)
三.源代码
源代码如下
import sys
import qtawesome
from PyQt5.QtCore import pyqtSignal, Qt, QSize, QPropertyAnimation, QAbstractAnimation
from PyQt5.QtGui import QPixmap, QIcon, QPainter, QPainterPath
from PyQt5.QtWidgets import QWidget, QLabel, QApplication, QToolButton, QVBoxLayout, QGraphicsOpacityEffect
from qtpy import QtCore, QtGui
import resource_rc
def rounded_pixmap(pixmap, radius):
rounded_pixmap = QPixmap(pixmap.size())
rounded_pixmap.fill(Qt.transparent)
painter = QPainter(rounded_pixmap)
painter.setRenderHint(QPainter.Antialiasing)
path = QPainterPath()
path.addRoundedRect(0, 0, pixmap.width(), pixmap.height(), radius, radius)
painter.setClipPath(path)
painter.drawPixmap(0, 0, pixmap)
return rounded_pixmap
class Hover_mormal_button(QToolButton):
def __init__(self, parent=None):
super(Hover_mormal_button, self).__init__()
self.setParent(parent)
self.normal_color = "white"
self.hovered_color = "#1fcf9e"
self.icon_name = ""
self.setCursor(Qt.PointingHandCursor)
self.setIconSize(QSize(20, 20))
def set_icon(self, icon_name, normal_color="", hovered_color=""):
self.icon_name = icon_name
if normal_color:
self.normal_color = normal_color
if hovered_color:
self.hovered_color = hovered_color
self.setIcon(QIcon(qtawesome.icon(self.icon_name, color=self.normal_color)))
def enterEvent(self, a0: QtCore.QEvent):
self.setIcon(QIcon(qtawesome.icon(self.icon_name, color=self.hovered_color)))
super(Hover_mormal_button, self).enterEvent(a0)
def leaveEvent(self, a0: QtCore.QEvent):
self.setIcon(QIcon(qtawesome.icon(self.icon_name, color=self.normal_color)))
class MyLabel(QLabel):
"""
帶圓角的label
"""
clicked = pyqtSignal() # 能够点击
hovered = pyqtSignal(bool)
def __init__(self, parent=None):
super(MyLabel, self).__init__(parent)
self.setParent(parent)
self.setScaledContents(True) # 实现缩放
def mousePressEvent(self, ev: QtGui.QMouseEvent):
self.clicked.emit()
def enterEvent(self, a0: QtCore.QEvent):
self.setCursor(Qt.PointingHandCursor) # 改变鼠标样式
self.hovered.emit(True)
def leaveEvent(self, a0: QtCore.QEvent):
self.hovered.emit(False)
class normalMusicWidget(QWidget):
"""
一般音乐组件
"""
style = """*{font-family:"微软雅黑";}#hover_label{background-color:black;color:white;border:1px solid black;border-radius:12px;padding-left:5px;margin-right:10px;margin-bottom:10px;}#name_label:hover{color:rgb(30,206,155)}#hover_btn_label{background-color:rgba(0,0,0,0.5);border:1px solid rgba(0,0,0,0.5);border-radius:10px;}#hover_btn{border:none;}"""
def __init__(self):
super(normalMusicWidget, self).__init__()
self.ui_init()
self.setStyleSheet(self.style)
self.slot_init()
def ui_init(self):
self.layout = QVBoxLayout()
self.cover_label = MyLabel(self)
self.cover_label.setObjectName("cover_label")
self.name_label = QLabel()
self.name_label.setWordWrap(False)
self.name_label.setCursor(Qt.PointingHandCursor)
self.name_label.setObjectName("name_label")
self.hover_btn_label = QLabel()
self.hover_btn_label.setObjectName("hover_btn_label")
self.hover_btn = Hover_mormal_button(self.hover_btn_label)
self.hover_btn.setObjectName("hover_btn")
self.hover_btn.setIconSize(QSize(55, 55))
self.hover_btn.set_icon("fa.play-circle")
self.hover_btn.setVisible(False)
self.hover_btn_label.setVisible(False)
top_hover_layout = QVBoxLayout(self.cover_label)
self.hover_label = QLabel()
self.hover_label.setObjectName("hover_label")
self.hover_label.setFixedSize(90, 34)
top_hover_layout.addWidget(self.hover_btn_label)
top_hover_layout.setSpacing(0)
top_hover_layout.setContentsMargins(0, 0, 0, 0)
hover_layout = QVBoxLayout(self.hover_btn_label)
hover_layout.addWidget(self.hover_btn)
hover_layout.setAlignment(self.hover_btn, Qt.AlignCenter | Qt.AlignCenter)
top_hover_layout.addWidget(self.hover_label)
top_hover_layout.setAlignment(self.hover_label, Qt.AlignRight | Qt.AlignBottom)
self.hover_label.setText("🎧34.6万")
self.name_label.setText("抖音热歌:超好听的店铺BGM")
self.cover_label.setPixmap(
rounded_pixmap(QPixmap(":res/rescourses/cover.png").scaled(167, 167), radius=10))
self.layout.addWidget(self.cover_label)
self.layout.addWidget(self.name_label)
self.layout.setStretch(0, 10)
self.setLayout(self.layout)
def slot_init(self):
self.cover_label.hovered.connect(lambda s: self.control_hover_layer(s))
def control_hover_layer(self, s):
"""
控制遮罩层
:return:
"""
self.hover_label.setVisible(not s)
effect = QGraphicsOpacityEffect(self)
self.hover_btn_label.setGraphicsEffect(effect)
animation = QPropertyAnimation(self)
animation.setTargetObject(effect)
animation.setPropertyName(b'opacity')
animation.setDuration(300)
animation.setKeyValueAt(0, 0)
animation.setKeyValueAt(1, 1)
if s:
animation.setDirection(QAbstractAnimation.Forward)
else:
animation.setDirection(QAbstractAnimation.Backward)
animation.start(QAbstractAnimation.DeleteWhenStopped)
self.hover_btn.setVisible(s)
self.hover_btn_label.setVisible(s)
if __name__ == '__main__':
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) # 高分辨率浏览器自适应
app = QApplication(sys.argv)
win = normalMusicWidget()
win.show()
sys.exit(app.exec_())
四.总结
本次详细讲解一个QQ音乐组件的制作,附上了源代码,UI设计流程大家可自行体会,只要慢慢拆解就没有难的布局,重点在实现思路。