当前位置: 首页 > news >正文

【实战项目】简易版的 QQ 音乐:一

> 作者:დ旧言~
> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:能自我实现简易版的 QQ 音乐。

> 毒鸡汤:有些事情,总是不明白,所以我不会坚持。早安!

> 专栏选自:实战项目_დ旧言~的博客-CSDN博客

> 望小伙伴们点赞👍收藏✨加关注哟💕💕

一、项目演示

演示效果

该项目是基于 QT 开发的音乐播放软件,界面友好,功能丰富,旨在加强同学们对 QT 知识的综合应用,以及熟悉 QT 项目开发的流程。主要功能如下:

窗口 head 部分:

  • 点击最小化按钮,窗口最小化
  • 点击最大化按钮,窗口示反应(即禁止窗口最大化)
  • 点击关闭按钮,程序退出
  • 点击皮肤按钮,更换皮肤(该功能暂未支持,uu 可私下扩展,最简单的方式就是更换窗体的背景颜色)
  • 搜索框搜索功能(该功能暂未支持,uu 可私下扩展)

窗口 body 左侧:

  • 点击推荐按钮,窗口右侧显示:推荐Page(暂只有页面)
  • 点击电台按钮,窗口右侧显示:电台Page(未支持)
  • 点击⾳乐馆按钮,窗口右侧显示:音乐馆Page(未支持)
  • 点击我喜欢按钮,窗口右侧显示:收藏的音乐Page
  • 点击本地下载按钮,窗口右侧显示:本地音乐Page
  • 点击最近播放按钮,窗口右侧显示:最近播放Page

注意:左侧按钮,当光标悬停时会有不同颜⾊突出显⽰,当点击时会有绿⾊显⽰,并且按钮的
右侧有跳动的竖条。

窗⼝右侧:

  • 点击全部播放按钮,播放当前页面列表中所有音乐
  • 双击列表中某音乐,播放当前选中音乐
  • 点击心支持收藏
  • ⽀持最近播放过音乐记忆

播放控制区:

  • 支持seek功能,即拖拽到歌曲指定位置播放
  • 支持:随机、单曲循环、循环播放
  • 支持播放上⼀曲
  • 支持播放上一曲
  • 支持播放下⼀曲
  • 支持播放和暂停
  • 支持音量调节和静⾳
  • 支持歌曲总时长显示 + 当前播放时间显示
  • 支持持 LRC 歌词同步显示

二、界面开发


2.1、界面简要分析

界⾯上控件比较多,归类之后主要分为两部分:head 区和 body 区。

head 区域从左往右依次为:图标、搜索框、更换皮肤按钮、最小化 & 最大化 & 退出按钮。

body 区域分为左侧种类选择区域和右侧 Page 展示区:

Body左侧区域有两部分组成:在线音乐 和 我的音乐,两部分内部的控件种类是相同的。

  • ① 说明区域,实际为 QLabel
  • ② 自定义控件(按钮的扩展):图片+文本+动画
  • ③ 同②,⾃定义控件(按钮的扩展):图片+文本+动画
  • ④ 同②,⾃定义控件(按钮的扩展):图片+文本+动画

Body 右侧区域由:Page 区、播放进度、播放控制区三部分构成。

  • ① Page区:歌曲信息页面,点击 < 或 > 具有轮播图效果
  • ② 播放进度:当前歌曲播放进度说明,支持seek功能,与播放控制区时间、以及LRC歌词是同步的
  • ③ 播放控制区域:显示歌曲图片 & 名称 & 歌手、播放模式 & 下⼀曲 & 播放暂停 & 上⼀曲 & ⾳量调节和静⾳ & 添加本地⾳乐、当前播放时间 / 歌曲总时长 & 弹出歌词窗口按钮

整个页面内容可以分为上下两组:今日为你推荐、你的歌曲补给站。两组的布局实际是相同的,元素:

  1. 上方显示 1 行,内部有4个推荐元素;下方显示 2 行,每行有 4 个推荐元素
  2. 左右两侧⼀个按钮,点击后推荐内容会更换下一批,不停点击会循环推荐
  3. 当鼠标悬停在推荐元素上时,推荐元素会向上移动,当鼠标离开时,又回到原位置
  4. 当鼠标悬停在推荐元素上时,同时会出现小手图标,说明该推荐元素具有点击功能

我喜欢、本地下载、最近播放类似下图:

这三个Page中布局、控件都是相同的,只是填充的数据不⼀样。每个Page中包含了多个控件,大致如下:

  • ① QLabel:类型说明
  • ② QLabel:图⽚显⽰
  • ③ QButton:播放全部按钮
  • ④ ⼀组QLabel说明:⾳乐、歌⼿、专辑
  • ⑤ QListWidget:播放列表

歌词页面:解析当前正在播放音乐的歌词,同步显示在界面上。

显示内容分为:歌曲信息、歌词部分、左上方收起隐藏按钮。

  • 歌曲信息由歌曲名称(QLabel)和歌手名称(QLabel)构成
  • 歌词部分展示当前在唱歌词(QLabel)和在唱部分前三行和后三行歌词(QLabel)展示,当前播放
  • 歌词突出显示
  • 点击收起按钮后,该页面会以动画的方式收起

说明:

  1. 当歌曲有LRC歌词时,播放时歌词会随播放时间自动调整;歌曲没有LRC歌词时,歌词部分显示空字符。
  2. 以上对本项目的界面进行了简单的说明,大家先有个初步了解,接下来利用QT Designer完成界⾯的布局。

2.2、界面开发


2.2.1、创建工程

创建一个基于 QWidget 的工程,选中生成 form 选项,将来界面部分主要使用 QDesigner 来设计:

2.2.2、主界面布局设置

基于Widget局部:

QT系统提供4种布局管理器:

  • QHBoxLayout:水平布局
  • QVBoxLayout:垂直布局
  • QGridLayout:栅格布局
  • QFormLayout:表单布局

为什么采用 widget 来设计:

由于⼀个 widget 中只能包含上述布局管理器中的⼀种,所以直接使用布局管理器来布局不是很灵活;而一个 widget 中可以包含多个 widget ,在 widget 中的控件可以进行水平、垂直、栅格、表单等布局操作,非常灵活。

窗口主框架设计:

主窗口的布局:

选中 QQMusic ,在弹出的属性中找到 geometry 属性,将窗口宽度修改为:1040,高度修改为700

从控件区拖拽⼀个 Widget 到窗口区域,objectName 修改为:background,选中 QQMusic ,然后点击垂直布局,background 就填充满了整个窗口。

为了看到效果,选中 backroound 控件,然后右键单击,弹出菜单中选择改变样式表,内部添加:

background-color:gray;

整个窗口由 head 和 body 上下两部分组成:

  • 直接拖两个 Widget 放到设计区,双击将名字修改为 head 和 body
  • 修改背景颜色方便查看效果,head 背景色修改为 green,body 背景色修改为 pink
background-color:green;
background-color:pink;

head 在上,body 在下,然后选中 background 对象,点击垂直布局:

head 和 body 平分了整个background,并且 head 和 body 的 margin 有间隔。再次选中background 对象,右侧属性部分下滑找到 Layout ,将 Margni 和 Space 修改为 0:

但是 head 占区域过大,选中 head 对象,将 head 的 minimumSize 和 maxmumSize 属性的高度都调整为 80 ,这样head的大小就固定了:

head 内部设计:

head 内部由两部分构成,headLeft 区域显⽰图标,headRight 区域为搜索框和功能按钮区域。拖两个 widget 到 head 中,将 objectName 修改为 headLeft 和 headRight ,背景颜色修改为:

然后选中 head 对象,点击水平布局(垂直布局左侧就是水平布局),就会呈现如下布局:

继续选中 head 对象,下滑找到 Layout 属性,将 Margin 和 Spacing 全部设置为 0 ,选中 headLeft 对象,将 minimumSize 和 maximumSize 的宽度修改为 200 ,就能看到 head 的初步效果:

headLeft:

拖⼀个 QLabel 控件放置 headLeft 内,将 QLabel 的 objectName 修改为 logo ,text 属性修改为空;然后选中 headLeft ,点击水平布局,此时 QLabel 就会填充满 headLeft 。同样需要选中headLeft,下滑找到 Layout 属性,将 Margin 和 spacing 全部设置为0

headRight:

headRight 内部也是由两部分构成:搜索框和按钮区域拖拽两个 widget 到 headRight ,修改objectName 为 SearchBox 和 SettingBox ,将 SearchBox 的 minimumSize 和 maximumSize 的宽度修改为 300,背景颜色分别修改为:

选中 headRight ,然后点击水平布局,并将 headRight 的 Margin 和 Spacing 修改为 0 ,就能看到下面的效果:

searchBox:

拖⼀个 QLineEdit 进去,然后选中 searchBox 点击水平布局

settingBox:

拖拽⼀个按钮到 SettingBox ,按钮的 minimumSize 和 maximumSize 的宽度和高度都修改为 30,然后鼠标选中,按着 ctrl键+鼠标 拖拽,复制 3 个出来摆放好,依次将四个按钮 objectName 从左往右修改为:skin、max、min、quit,并将按钮的 text 属性也修改为空,将来设置图片。在控件区域找到 Spacers ,找到 Horizontal Spacer 控件,拖拽到 SettingBox 区域。

选中 SettingBox ,点击水平布局,并将 SettingBox 的 Margin 和 Spacing 修改为 0

Body部分布局:

整个body部分是由bodyLeft和bodyRight两部分组成:

① 拖两个 Widget 到 Body 中,将 objectName 修改为 bodyLeft 和 bodyRight

② 将 bodyLeft 颜色修改为:

③ 选中 body,点击水平布局,将 bodyLeft 的 minimumSize 和 maxmumSize 的宽度修改为 200

④ 选中 Body,将 body 的 Margin 和 Spacing 修改为 0

bodyLeft 内部布局:

  1. 拖拽⼀个 Widget 到 bodyLeft ,将 objectName 修改为 leftBox ,背景颜⾊修改为:background-color:pink.
  2. 拖拽 Vertical Spacer 到 bodyLeft.
  3. 选中 leftBox ,将 minmumSize 和 maxmumSize 的高度修改为 400.
  4. 选中 bodyLeft ,点击垂直布局,并将 bodyLeft 的 Margin 和 Spacing 修改为 0.

leftBox 内部布局:

leftBox内部包含:在线音乐 和 我的音乐 两部分。

① 拖拽两个 Widget 到 leftBox 中,将 objectName 依次修改为:onlineMusic 和 myMusic

② 颜色分别修改为:

③ 选中 leftBox ,点击垂直布局,然后将 Margin 和 Spacing 设置为 0

④ onlineMusic 和 myMusic 内部的元素都是相同的,由⼀个 QLabel 和三个 Widget 构成,后期Widget 会替换为自定义按钮,此处先用 Widget 占位。因此分别向 onlineMusic 和 myMusic 内部拖拽⼀个 QLabel 和三个 QWidget ,并选中 onlineMusic 和 myMusic 点击垂直布局,然后将Margin 和 Spacing 设置为 0

bodyRight布局:

bodyRight 由层叠窗口、进度滑竿、播放控制区三部分组成:

① 拖拽层叠窗口控件 Stacked Widget ,就在 Widget 控件上方到 bodyRight 中

② 拖拽 Widget 到 bodyRight ,将 objectName 修改为 processBar,将 minimumSize 和maximumSize 的高度修改为 30 ,背景颜色修改为绿色。

③ 拖拽 Widget 到 bodyRight ,将 objectName 修改为 controlBox ,将 minmumSize 高度修改为 60

④ 选中 bodyRight ,点击垂直布局,然后将 bodyRight 的 Margin 和 Spacing 修改为 0

⑤ 为了能看到效果,将 processBar 颜色修改为:background-color:pink.

stackedWidget 内部增加页面:

stackedWidget 默认会提供两个页面,还需添加四个页面。

在对象区域选中 stackedWidget 控件,然后右键单击弹出菜单中选择添加页:

以类似的方式添加添加 4 个页面,并修改每个页面的 objectName 如下:

选中 stackedWidget ,然后右键单击,弹出菜单中选择:改变页顺序,在弹出的窗口中就能看到每个页面的索引:

六个页面中,recPage 页面需要实现,musicPage、radioPage 暂未支持,同学们可自行扩展likePage、localPage、recentPage 三个页面都是雷同的,将来自定义即可。

ControlBox内部布局:

该区域内部由三部分组成:歌曲信息部分、播放控制部分、时间显示

① 拖拽三个 Widget 到 ControlBox 中,将 ObjectName 依次修改为 play_1、play_2、play_3 颜色依次修改为:

② 选中 ControlBox ,点击水平布局,将 ControlBox 的 Margin 和 Spacing 修改为 0

play1 内部:

拖拽 3 个 QLabel ,放置歌曲图片、歌手名和歌曲名字,调整好位置,将 QLabel 的 objectName 修改为:musicCover、musicName、musicSinger然后选中 play1 ,点击栅格布局

play2 内部:

QWidget::setWindowFlag(...): 设置窗⼝格式,⽐如创建⽆边框的窗⼝

从左到右依次摆放 6 个按钮,按钮的 minimumSize 和 maxmumSize 均修改为 30 * 30 ,将objectName 从左往右依次修改为:playMode、playUp、Play、playDown、volume、addLocal;然后选中 play2 ,点击水平布局,并将 play_2 的 Margin 和 Spacing 修改为 0.

play3 内部:

拖四个 QLabel 和一个按钮,调整大小位置,从左往右 QLabel 的 objectName 依次修改为:labelNull、currentTime、line、totalTime,按钮的 objectName 修改为 lrcWord ,按钮的maxmumSize 的宽度和高度修改为 30*30 ;选中 play3 ,点击水平布局,并将 play2 的 Margin 和Spacing 修改为 0.

2.3、界面美化


2.3.1 、主窗口设定

设置窗口:

仔细观察发现主窗口是没有标题栏,因此在窗口创建前,就需要设置下窗口的格式:

QWidget::setWindowFlag(...): 设置窗⼝格式,⽐如创建⽆边框的窗⼝

由于窗口中控件比较多,这些控件将来都需要初始化,如果将所有代码放在 QQMusic 的构造函数中实现,将来会造成构造函数非常臃肿,因此在 QQMusic 类中添加 initUI() 方法来完成界面初始化工作:

// QQMusic.h ⽂件中添加:
void initUI();
// 添加完成后,光标放在函数名字上按 alt + Enter 组合键完成⽅法定义
// QQMusic.cpp 头⽂件中完成定义void QQMusic::initUI()
{// 设置⽆边框窗⼝,即窗⼝将来⽆标题栏setWindowFlag(Qt::WindowType::FramelessWindowHint);
}

运行后,发现有以下两个问题:

  1. 窗口无标题栏,找不到关闭按钮,导致窗口无法关闭
  2. 窗口无法拖拽

关闭窗口,可以先将光标放在任务栏中当前应用程序图标上,弹出的框中选择关闭,后序会实现关闭功能:

主界面无法拖动,此时只需要处理下鼠标单击 (mousePressEvent) 和 鼠标移动(mouseMoveEvent) 事件即可。鼠标左键按下时,记录下窗口左上角和鼠标的相对位置鼠标移动时,会产生新的位置,保持鼠标和窗口左上角相对位置不变,通过 move 修改窗口的左上叫坐标即可:

/
// QQMusic.h中添加
protected:
// 重写QWidget类的⿏标单击和⿏标滚轮事件
void mousePressEvent(QMouseEvent *event)override;
void mouseMoveEvent(QMouseEvent* event)override;
// 记录光标相对于窗⼝标题栏的相对距离
QPoint dragPosition;
/
// QQMusic.cpp中添加void QQMusic::mousePressEvent(QMouseEvent *event)
{// 拦截⿏标左键单击事件if(event->button() == Qt::LeftButton){// event->globalPos():⿏标按下事件发⽣时,光标相对于屏幕左上⻆位置// frameGeometry().topLeft(): ⿏标按下事件发⽣时,窗⼝左上⻆位置// geometry(): 不包括边框及顶部标题区的范围// frameGeometry(): 包括边框及顶部标题区的范围// event->globalPos() - frameGeometry().topLeft() 即为:// ⿏标按下时,窗⼝左上⻆和光标之间的距离差// 想要窗⼝⿏标按下时窗⼝移动,只需要在mouseMoveEvent中,让光标和窗⼝左上⻆保持相同的位置差// 获取⿏标相对于屏幕左上⻆的全局坐标dragPosition = event->globalPos() - frameGeometry().topLeft();return;}QWidget::mousePressEvent(event);
}void QQMusic::mouseMoveEvent(QMouseEvent *event)
{if(event->buttons() == Qt::LeftButton){// 根据⿏标移动更新窗⼝位置move(event->globalPos() - dragPosition);return;}QWidget::mouseMoveEvent(event);
}

给窗口添加阴影需要用到 QGraphicsDropShadowEffect 类,具体步骤如下:

  1. 创建 QGraphicsDropShadowEffect 类对象
  2. 设置阴影的属性。比如:设置阴影的偏移、颜色、圆角等
  3. 阴影设置到具体对象上

在 initUI() 函数中添加如下代码:

// 设置窗⼝背景透明
this->setAttribute(Qt::WA_TranslucentBackground);// 给窗⼝设置阴影效果
QGraphicsDropShadowEffect* shadowEffect = new
QGraphicsDropShadowEffect(this);
shadowEffect->setOffset(0,0);// 设置阴影偏移
shadowEffect->setColor("#000000");// 设置阴影颜⾊:⿊⾊
shadowEffect->setBlurRadius(10);// 设置阴影的模糊半径
this->setGraphicsEffect(shadowEffect);

2.3.2、添加图片资源

添加一个 qrc 文件,将图片资源拷贝到工程目录下,并添加到工程中:

将之前布局时所有按钮的背景全色全部清除掉,按照下面的风格重新设定。

2.3.3、head 处理

颜色查看器:

三、自定义控件


3.1、BtForm


3.1.1、BtForm 界面设计

添加⼀个新设计界面,命名为BtForm:

该控件实际由:

图片、文字、动画三部分组成。图片和文字分别用QLabel展示,动画部分内部实际为 4 个QLabel 。

① 将 BtForm 的 geometry 的宽度和高度修改为 200*35。

② 拖⼀个 Widget 到 btForm 中,objectName 修改为 btStyle,将 btForm 的 margin 和 Spacing 设置为 0.

③ 拖 2 个 QLable 和 1 个 Widget 到 btStyle 中,并将 objectName 依次修改为 btIcon、btText、lineBox

  • btIcon 的 minimumSize 和 maximumSize 的宽度设置为 30 (为了看到效果可将颜色设置为red)
  • btText 的 minimumSize 和 maximumSize 的宽度设置为 90 (为了看到效果可将颜色设置为green)
  • lineBox的 minimumSize 和 maximumSize 的宽度设置为 30
  • 然后选中 btStyle,并将其 margin 和 Spacing 设置为 0

④ 然后往 lineBox 内部拖 4 个 QLabel,objectName 依次修改为 line1、line2、line3、line4,minimumSize 和 maximumSize 的宽度均设置为 2

将 bodyLeft 内部 onlineMusic 和 MyMusic 中的 QWidget 全部提升为 BtForm。具体操作:

选中要提升的控件,比如:Rec,在弹出的菜单中选择提升为,会出现⼀个新窗口(如下右侧图),在提升的类名称中输入要提升为的类型 BtForm,然后点击添加,最后选中 btform.h 点击提升,便可以将 Rec 由 QWidget 提升为自定义的 BtForm 类型。

3.1.2、BtForm 类中实现

设置按钮上的图片和文字信息,以及该按钮关联的 page 页面:

// btform.h 新增
// 按钮id:该按钮对应的page⻚
int id = 0;
// 设置图标 ⽂字 id
void seticon(QString btIcon,QString content,int mid);// btform.cpp新增
void btFrom::seticon(QString btIcon,QString btText,int mid)
{// 设置⾃定义按钮的图⽚、⽂字、以及idui->btIcon->setPixmap(QPixmap(btIcon));ui->btText->setText(btText);this->id = mid;
}

在 QQMusic.cpp 的 initUI() 函数中新增:

void Widget::initUi()
{// ...// 设置BodyLeft中6个btForm的信息ui->rec->seticon(":/images/rec.png", "推荐", 1);ui->music->seticon(":/images/music.png", "⾳乐馆", 2);ui->audio->seticon(":/images/radio.png", "电台", 3);ui->like->seticon(":/images/like.png", "我喜欢", 4);ui->local->seticon(":/images/local.png", "本地下载", 5);ui->recent->seticon(":/images/recent.png", "最近播放", 6);
}

按钮响应:重写鼠标 mousePressEvent,当按钮按下时

①:按钮颜色发生变化

②:给 QQMusic 类发送 click 信号

// btform.h 新增
protected:// ⿏标点击事件virtual void mousePressEvent(QMouseEvent *event);// btform.cpp新增
void btFrom::mousePressEvent(QMouseEvent *event)
{// 告诉编译器不要触发警告(void)event;// ⿏标点击之后,背景变为绿⾊,⽂字变为⽩⾊ui->btStyle->setStyleSheet("#btStyle{ background:rgb(30,206,154);}*{color:#F6F6F6;}");// 发送⿏标点击信号emit click(this->id);
}

③ QQMusic 类处理该信号,内部:实现窗口切换,并清除上次按钮点击留下的样式,因 QQMuisc 中需要新增:

// qqmusic.h 新增// btForm点击槽函数
void onBtFormClick(int id);// qqmusic.cpp 新增
void QQMusic::connectSignalAndSlot()
{// ...// ⾃定义的btFrom按钮点击信号,当btForm点击后,设置对应的堆叠窗⼝connect(ui->rec, &btFrom::click, this, &QQMusic::onBtFormClick);connect(ui->musics, &btFrom::click, this, &QQMusic::onBtFormClick);connect(ui->audio, &btFrom::click, this, &QQMusic::onBtFormClick);connect(ui->like, &btFrom::click, this, &QQMusic::onBtFormClick);connect(ui->local, &btFrom::click, this, &QQMusic::onBtFormClick);connect(ui->recent, &btFrom::click, this, &QQMusic::onBtFormClick);
}void Widget::onBtFormClick(int id)
{// 1.获取当前⻚⾯所有btFrom按钮类型的对象QList<BtForm*> buttonList = this->findChildren<BtForm*>();// 2.遍历所有对象, 如果不是当前id的按钮,则把之前设置的背景颜⾊清除掉foreach (BtForm* btitem, buttonList){if (id  != btitem->getId()){btitem->clearBg();}}// 3.设置当前栈空间显⽰⻚⾯ui->stackedWidget->setCurrentIndex(id - 1);
}

④ BtForm类中新增:
 

// btform.h 新增
public:// 清除上⼀次按钮点击留下的样式void clearBg();// 获取idint getId();// btform.cpp 新增:
void BtForm::clearBg()
{// 清除上⼀个按钮点击的背景效果,恢复之前的样式ui->btStyle->setStyleSheet("#btStyle:hover{ background:#D8D8D8;} ");
}int BtForm::getId()
{return id;
}

为了能看到 Page 切换的效果,可以在 stackedWidget 的每个 page 上放⼀个 QLabel 说明。

BtFrom上的动画效果:

Qt 中 QPropertyAnimation 类可以提供简单的动画效果,允许对 QObject 获取派生类的可读写属性进行动画处理,创建平滑、连续的动画效果,比如控件的位置、大小、颜色等属性变化,使用时需包含<QPropertyAnimation>。

lineBox 中的 line1、line2、line3、line4 添加动画效果,BtForm 类中增加如下代码:

    // btform.h 新增:// linebox动画起伏效果QPropertyAnimation *animationLine1;QPropertyAnimation *animationLine2;QPropertyAnimation *animationLine3;QPropertyAnimation *animationLine4;// btform.cpp的构造函数中新增:
BtForm::BtForm(QWidget *parent) :QWidget(parent),ui(new Ui::BtForm)
{ui->setupUi(this);// 设置line1的动画效果line1Animal = new QPropertyAnimation(ui->line1, "geometry", this);line1Animal->setDuration(1500);line1Animal->setKeyValueAt(0, QRect(0, 15, 2, 0));line1Animal->setKeyValueAt(0.5, QRect(0, 0, 2, 15));line1Animal->setKeyValueAt(1, QRect(0, 15, 2, 0));line1Animal->setLoopCount(-1);line1Animal->start();// 设置line2的动画效果line2Animal = new QPropertyAnimation(ui->line2, "geometry", this);line2Animal->setDuration(1600);line2Animal->setKeyValueAt(0, QRect(7, 15, 2, 0));line2Animal->setKeyValueAt(0.5, QRect(7, 0, 2, 15));line2Animal->setKeyValueAt(1, QRect(7, 15, 2, 0));line2Animal->setLoopCount(-1);line2Animal->start();// 设置line3的动画效果line3Animal = new QPropertyAnimation(ui->line3, "geometry", this);line3Animal->setDuration(1700);line3Animal->setKeyValueAt(0, QRect(14, 15, 2, 0));line3Animal->setKeyValueAt(0.5, QRect(14, 0, 2, 15));line3Animal->setKeyValueAt(1, QRect(14, 15, 2, 0));line3Animal->setLoopCount(-1);line3Animal->start();// 设置line4的动画效果line4Animal = new QPropertyAnimation(ui->line4, "geometry", this);line4Animal->setDuration(1800);line4Animal->setKeyValueAt(0, QRect(21, 15, 2, 0));line4Animal->setKeyValueAt(0.5, QRect(21, 0, 2, 15));line4Animal->setKeyValueAt(1, QRect(21, 15, 2, 0));line4Animal->setLoopCount(-1);line4Animal->start();
}

关于动画显示:

动画并不是所有页面都显示,只有当前选中的页面显示,所以默认情况下,动画隐藏。默认情况下设置 addlocal 显示。

// btform.h 新增:
// 显⽰动画效果
void showAnimal();// btform.cpp的中新增:
void btFrom::showAnimal()
{// 显⽰linebox, 设置颜⾊为绿⾊ui->linebox->show();
}// QQMusic的initUI中设置默认选中
void QQMusic::initUi()
{// ...// 本地下载BtForm动画默认显⽰ui->local->showAnimal();ui->stackedWidget->setCurrentIndex(4);
}

3.2、推荐页面


3.2.1、推荐页面分析

仔细观察推荐页面,对其进行拆解发现,推荐页面由五部分构成:

① "推荐"文本提示,即 QLabel

② "今日为你推荐"文本提示,即 QLabel

③ 具体推荐的歌曲内容,点击左右两侧翻页按钮,具有轮番图效果,将光标放到图上,有图片上移动画

④ "你的歌曲补给站"文本提示,即 QLabel

⑤ 具体显示音乐,和③实际是⼀样的,不同的是③中音乐只有⼀行,⑤中的音乐有两行因为页面中元素较多,直接摆到⼀个页面太拥挤,从右侧的滚动条可以看出,整个页面中的元素都放置在 QScrollArea 中。仔细分析③发现,里面包含了:

3.2.2、推荐页布局

在 stackedWidget 中选中推荐页面,objectName 为 recPage 的页面,删掉之前添加的 QLabel 推荐提示:

① 拖拽一个 QScrollArea 到 recPage 中,geometry 的宽度和高度修改为 820 和 500,

② 拖拽一个 QLable,objectName 修改为 recText,显示内容修改为推荐,minimumSize 和maximumSize 的高度均修改为 50,Font 大小修改为 24

③ 再拖拽一个 QLable 和 Widget ,QLable 的 objectName 修改为 recMusictext,内容修改为"今日为你推荐",minimumSize 和maximumSize 的高度均修改为 30,Font 大小修改为 18;Widget 的 objectName 修改为 recMusicBox

④ 再拖拽一个 QLabel 和 Widget ,QLabel 的 objectName 修改为 supplyMusicText ,内容修改为"你的音乐补给站", minimumSize 和 maximumSize 的高度均修改为 30,Font 大小修改为18;Widget 的 objectName 修改为 supplyMusicBox。

⑤ 最后选中 QScrollArea,点击垂直布局。

3.2.3、自定义 recBox

RecBox 界面布局:

① 新添加设计师界面,命名为 RecBox。geometry 的宽高修改为:685*440。

② 添加三个Widget,objectName 依次修改为 leftPage、musicContent、rightPage;leftPage 和 rightPage 的 minimumSize 和 maximumSize 修改宽为 30,然后选中 RecBox 点击水平布局。将RecBox 的 margin 和 Spacing 修改为 0

③ 在 upPage 和 downPage 中各拖⼀个按钮,upPage 中按钮 objectName 修改为btUpminimumSize的高度修改为220;downPage 中按钮 objectName 修改为 btDownminimumSize 的高度修改为 220 ;然后选中 upPage 和 downPage 点击水平布局。将upPagedownPage 和的 margin 和 Spacing 修改为 0。

④ 在 musicContent 中拖两个 Widget,objectName 依次修改为 recListUp 和 recListDown,然后选中 musicContent 点击垂直布局,将 musicContent 的 margin 和 Spacing 修改为 0。(为了看清楚效果可临时将 recListUp 背景色设置为:background-color:green; 将 recListDown 背景色设置为:background-color:red;)

⑤ 在 recListUp 和 recListDown 中分别拖两个水平布局器,依次命名为 recListUpHLayout 和
recListDownHLayout,选中 recListUp 和 recListDown 点击水平布局,将 margin 和 Spacing 修改为 0 按钮添加如下 QSS 美化:

3.2.4、自定义 recBoxItem

RecBoxItem 界面布局:添加⼀个 Designer 界面,命名为 RecBoxItem,geometry 的宽和高设置为:150 * 200。

① 拖拽⼀个 Widget 到 RecBoxItem 中,objectName 修改为 musicImageBox,minimumSize 和maximumSize 的高度均修改为150;

② 拖拽⼀个 QLabel 到 Widget 中,objectName 修改为 recBoxItemText ,文本设置为"荐-001",QLabel 的 alignment 属性设置为水平、垂直居中。

③ 拖拽⼀个 QLabel 到 musicImageBox 中,objectName 修改为 recMusicImage, geometry 设置为:[(0, 0), 150*150]

④ 拖拽一个 QPushButton 到 musicImageBox 中,objectName 修改为 recMusicBtn ,删除掉文本内容。在属性中找到 cursor,点击选择小手图标

RecBoxItem 测试:

// recBox.cpp构造函数中添加如下代码
RecBoxItem* item = new RecBoxItem();
ui->recListUpHLayout->addWidget(item);

RecBoxItem 类中添加动画效果:

  • 在RecBoxItem类中拦截鼠标进入和离开事件,在进入时让图片上移,在离开时让图片下移回到原位
  • 该类中还需要添加设置推荐文本和图片的方法,将来需要在外部来设置每个 RecBoxItem 的文本和图片

3.2.5、RecBox 添加 RecBoxItem

图片路径和推荐文本准备:

每个 RecBoxItem 都有对应的图片和推荐文本,在往 RecBox 中添加 RecBoxItem 前需要先将图片路径和对应文本准备好。由于图片和文本具有对应关系,可以以键值对方式来进进组织,以下实现的时采用 QT 内置的 QJsonObject 对象管理图片路径和文本内容。

QJsonObject类:
头⽂件: <QJsonObject>
// 功能: 插⼊<key, value>键值对,如果key已经存在,则⽤value更新与key对应的value
// 返回值:返回指向新插⼊项的键值对
QJsonObject::iterator insert(const QString &key, const QJsonValue &value);
// 功能:获取与key对应的value
// 返回值:返回的value⽤QJsonValue对象组织
QJsonValue QJsonObject::value(const QString &key) constQJsonArray类:
作⽤:管理的是QJsonValue对象
头⽂件:<QJsonArray>
该类重载了[]运算符,可以通过下标⽅式获取管理的QJsonValue对象
QJsonValue operator[](int i) const
QJsonValueRef operator[](int i)
// 往QJsonArray中插⼊⼀个QJsonValue对象
void append(const QJsonValue &value)QJsonValue类
// 单参构造⽅法,将QJsonObject对象转换为QJsonValue对象
QJsonValue(const QJsonObject &o)
// 将内部管理的数据转化成QJsonObject返回
QJsonObject toObject() const
// 将内部管理的数据转化成QString返回
QString toString() const

recBox 中添加元素:

由于 recPage 页面中有两个 RecBox 控件,上面的 RecBox 为一行四列,下方的 RecBox 为 2 行四列,因此在 RecBox 类中增加以下成员变量:

// RecBox.h 新增
#include <QJsonArray>
public:void initRecBoxUi(QJsonArray data, int row);private:int row;// 记录当前RecBox实际总⾏数int col;// 记录当前RecBox实际每⾏有⼏个元素QJsonArray imageList; // 保存界⾯上的图⽚, ⾥⾯实际为key、value键值对

3.2.6、RecBox 中 btUp 和 btDown 按钮 clicked 处理

添加槽函数:

选中 recbox.ui 文件,分别选中 btUp 和 btDown,右键单击弹出菜单选择转到槽,选中 clicked 确定,btUp 和 btDown 的槽函数就添加好了。

void RecBox::on_btUp_clicked()
{
// 点击btUp按钮,显⽰前4张图⽚,如果已经是第⼀张图⽚,循环从后往前显⽰
}
void RecBox::on_btDown_clicked()
{
// 点击btUp按钮,显⽰前8张图⽚,如果已经是第⼀张图⽚,循环从后往前显⽰
}

imageList 中图片分组:

假设 imageList 中 有 24 组图片路径和推荐文本信息,如果将信息分组:

  • 如果是 recMusicBox ,将元素按照 co l分组,即每 4 个元素为⼀组,可分为 6 组;如果是supplyMuscBox,将元素按照 col 分组,即每 8 个元素为一组,可分为 3 组。
  • RecBox 类中添加 currentIndex 和 count 整形成员变量,currentIndex 记录当前显示组,count 记录总的信息组数。当点击 btUp 时,currentIndex--,显示前一组,如果 currentIndex小于 0 时,将其设置为 count-1;当点击 btDown 按钮时,currentIndex++ 显示下⼀组,当currentIndex为count 时,将 count 设置为 0.

元素重复分析:

每次 btUp 和 btDown 点击后,应该显示前一组和后一组图片,由于之前 recListUpHLayout 和
recListDownHLayout中 已经有元素了,因此需要先将之前的元素删除掉。

void RecBox::createRecBoxItem()
{// 溢出掉之前旧元素QList<RecBoxItem*> recUpList = ui->recListUp->findChildren<RecBoxItem*>();for(auto e : recUpList){ui->recListUpHLayout->removeWidget(e);delete e;}
QList<RecBoxItem*> recDownList = ui->recListDown->findChildren<RecBoxItem*>
();for(auto e : recDownList){ui->recListDownHLayout->removeWidget(e);delete e;}// 创建RecBoxItem对象,往RecBox中添加// ...
}

按照分组计算 imageList 中元素偏移:

程序启动时图片随机显示:

仔细观察发现,每次程序启动时,显示的图片都是相同的,这是因为 random_shuffle 在随机打乱元素时,需要设置随机数种子,否则默认使用的种子是相同的,就导致每次打乱的结果都是相同的,所以每次程序启动时 RecBox 中显示的内容都是相同的,因此在 randomPiction() 调用之前需要设置随机数种子。

// QQMusic类的initUi函数中新增
void QQMusic::initUi()
{// ...// 本地下载BtForm动画默认显⽰ui->local->showAnimal();ui->stackedWidget->setCurrentIndex(4);// 设置RecBox图⽚、⾏数srand(time(NULL));ui->recMusicBox->initRecBoxUi(randomPiction(), 1);ui->supplyMuscBox->initRecBoxUi(randomPiction(), 2);
}

3.3、自定义 CommonPage


3.3.1、CommonPage 页面分析

我的音乐下的:

我喜欢、本地下载、最近播放三个按钮表面上看对应三个 Page 页面,分析之后发现,这三个Page 页面实际是雷同的,因此只需要定义⼀个页面 CommonPage ,将 stackedWidget 中这三个页面的类型提升为 CommonPage 即可。

上图为本地音乐的 Page 页面,对页面拆解后,发现该页面可以分四部分:

① 页面说明,比如:本地音乐,该部分实际就是 QLabel 的提示说明;

② 正在播放音乐图片和播放全部按钮;

③ 音乐列表中每个部分的文本提示,实际就是三个 QLabel

④ 本页面对应的音乐列表,即 QListWidget 。

3.3.2、CommonPage 页面布局

新增加一个设计界面,objectName 修改为 CommonPage,geometry 的宽高修改为 800*500:

① 拖拽⼀个QLabel、两个 Widget 和⼀个 List View 控件到 CommonPage 中,objectName 从上往下依次修改为 pageTittle 、musicPlayBox、listLabelBox、pageMusicList,然后选中CommonPage 点击垂直布局:

  • 将 CommonPage 的 margin 和 Spacing 修改为 0。
  • pageTittle 的 minimumSize 和 maximumSize 的高度修改为 30。
  • musicPlayBox 的 minimumSize 和 maximumSize 的高度修改为 150。
  • listLabelBox 的 minimumSize 和 maximumSize 的高度修改为 40。

② 将 pageTittle 的文本内容修改为"本地音乐"

③ musicPlayBox 中拖拽一个 QLabel,objectName 修改为 musicImageLabel,minimumSize和 maximumSize 的宽度修改为 150

④ listLabelBox 中拖拽三个 QLabel,内容依次修改为:歌曲名称、歌手名称、专辑名称

⑤ 选中 List View,右键单击弹出菜单中选择"变形为",选择 QListWidget

3.3.3、CommonPage 界面设置和显示

CommonPage 页面是我喜欢、本地下载、最近播放三个界面的共同类型,因此该类需要提供设置:pageTittle 和 musicImageLabel 的公共方法,将来在程序启动时完成三个界面信息的设置,因此CommonPage 类需要添加一个 public 的 setCommonPageUI 函数

// commonpage.h 中新增
public:void setCommonPageUI(const QString &title, const QString &image);// commonpage.cpp 中新增
void CommonPage::setCommonPageUI(const QString &title, const QString &image)
{// 设置标题ui->pageTittle->setText(title);// 设置封⾯栏ui->musicImageLabel->setPixmap(QPixmap(image));ui->musicImageLabel->setScaledContents(true);
}

界面设置的函数需要在程序启动时就完成好配置,即需要在 QQMusic 的 initUi() 函数中调用完成设置:

void Widget::initUi()
{....// 设置我喜欢、本地⾳乐、最近播放⻚⾯ui->likePage->setCommonPageUI("我喜欢", ":/images/ilikebg.png");ui->localPage->setCommonPageUI("本地⾳乐", ":/images/localbg.png");ui->recentPage->setCommonPageUI("最近播放", ":/images/recentbg.png");
}

3.4、自定义 ListItemBox


3.4.1、ListItemBox 页面分析

CommonPage 页面创建好之后,等音乐加载到程序之后,就可以将音乐信息往 CommonPage 的 pageMusicList 中显示了。

上图每行都是 QListWidget 中的一个元素,每个元素中包含多个控件:

① 收藏图标,即QLabel

② 歌曲名称,即QLabel

③ VIP 和 SQ,VIP 即收费会员专享,SQ 为无损音乐,也是两个 QLabel

④ 歌手名称,即 QLabel

⑤ 音乐专辑名称,即 QLabel

此处,需要将上述所有 QLabel 组合在⼀起,作为一个独立的控件,添加到 QListWidget 中,因此该控件也需要自定义。

3.4.2、ListItemBox 页面布局 

添加一个设计师界面,objectName 为 ListItemBox,geometry 的宽度和高度修改为 800*45:

3.4.3、ListItemBox 显示测试

ListItemBox 将来要添加到 CommonPage 页面中的 QListWidget 中,因此在 CommonPage 类的初始化方法中添加如下代码:

#include "listitembox.h"void CommonPage::setCommonPageUI(const QString &title, const QString &image)
{// 设置标题ui->pageTittle->setText(title);// 设置封⾯栏ui->musicImageLabel->setPixmap(QPixmap(image));ui->musicImageLabel->setScaledContents(true);// 测试ListItemBox* listItemBox = new ListItemBox(this);QListWidgetItem* listWidgetItem = new QListWidgetItem(ui->pageMusicList);listWidgetItem->setSizeHint(QSize(ui->pageMusicList->width(), 45));ui->pageMusicList->setItemWidget(listWidgetItem, listItemBox);
}

3.4.4、支持 hover 效果

ListItemBox 添加到 CommonPage 中的 QListWidget 之后,自带 hover 效果,但是背景颜色和界面不太搭配,此处重新实现 hover 效果,此处重写 enterEvent 和 leaveEvent 来实现 hover 效果。

// listitembox.h 新增
protected:
void enterEvent(QEvent *event);
void leaveEvent(QEvent *event);// listitembox.cpp 新增
void ListItemBox::enterEvent(QEvent *event)
{(void)event;setStyleSheet("background-color:#EFEFEF");
}
void ListItemBox::leaveEvent(QEvent *event)
{(void)event;setStyleSheet("");
}

3.5、自定义 MusicSlider

由于 QT 内置的 Horizontal Slider (水平滑竿)不是很好看,该控件也采用自定义。

① 添加⼀个设计师界面,objectName 修改为 MusicSlider,geometry 修改为 800*20。

② 拖拽⼀个 QFrame,objectName 修改为 inLine,geometry 修改为 [(0,8), 800*4]。

③ 拖拽⼀个 QFrame,objectName 修改为 outLine,geometry 修改为 [(0,8), 400*4]。

④ 选中 MusicSlider,点击水平布局。

⑤ inLine 和 outLine 的样式设置如下:

⑥ 打开 QQMusic.ui,选中 progressBar 清除之前样式,将 progressBar 提升为 MusicSlider ,运行程序就能看到效果。

3.6、自定义 VolumeTool


3.6.1、VolumeTool 控件分析

音量调节控件本来也可以使用 Qt 内置的垂直滑杆来代替,只是垂直滑杆不好看,因此也自定义:

① 内部为类似 MusicSlider 控件 + 小圆球,圆球实际为一个 QPushButton[661630]

② 音量大小文本显示,实际为 QLabel

③ QPushButton ,点击之后在静⾳和取消静音切换

④ 一个倒三角,Qt 未提供三角控件,该控件需要手动绘制,用来提示是播放控制区那个按钮按下的

3.6.2、VolumeTool 界面布局

3.6.3、界面设置

该控件属于弹出窗口,即点击了主界面的音量调节按钮后,才需要弹出该界面,点击其他位置该界面自动隐藏。因此在窗口创建时,需要设置窗口为无边框以及为弹出窗口。

// VolumeTool.cpp 的构造函数中添加如下代码
#include <QGraphicsDropShadowEffect>VolumeTool::VolumeTool(QWidget *parent) :QWidget(parent),ui(new Ui::VolumeTool)
{ui->setupUi(this);setWindowFlags(Qt::Popup | Qt::FramelessWindowHint|Qt::NoDropShadowWindowHint);// 在windows上,设置透明效果后,窗⼝需要加上Qt::FramelessWindowHint格式// 否则没有控件位置的背景是⿊⾊的// 由于默认窗⼝有阴影,因此还需要将窗⼝的原有的阴影去掉,窗⼝需要加上Qt::NoDropShadowWindowHintsetAttribute(Qt::WA_TranslucentBackground);// ⾃定义阴影效果QGraphicsDropShadowEffect* shadowEffect = newQGraphicsDropShadowEffect(this);shadowEffect->setOffset(0, 0);shadowEffect->setColor("#646464");shadowEffect->setBlurRadius(10);setGraphicsEffect(shadowEffect);// 给按钮设置图标ui->silenceBtn->setIcon(QIcon(":/images/volumn.png"));// ⾳量的默认⼤⼩是20ui->outLine->setGeometry(ui->outLine->x(), 180 - 36 - 25, ui->outLine->width(), 20);ui->silderBtn->move(ui->silderBtn->x(), ui->outLine->y() - ui->silderBtn->height()/2);ui->volumeRatio->setText("20%");
}

3.6.4、界面创建及弹出

音量调节属于主界面上元素,因此在 QQMusic 类中需要添加 VolumeTool 的对象,在 initUi 中 new 该类的对象。主界面中音量调节按钮添加 clicked 槽函数。

// qqmusic.h中新增
#include "volumetool.h"
VolumeTool* volumeTool;// qqmusic.cpp中新增
void QQMusic::initUi()
{// ...// 创建⾳量调节窗⼝对象并挂到对象树volumeTool = new VolumeTool(this);
}void QQMusic::on_volume_clicked()
{// 先要调整窗⼝的显⽰位置,否则该窗⼝在主窗⼝的左上⻆// 1. 获取该按钮左上⻆的图标QPoint point = ui->volume->mapToGlobal(QPoint(0,0));// 2. 计算volume窗⼝的左上⻆位置// 让该窗⼝显⽰在⿏标点击的正上⽅// ⿏标位置:减去窗⼝宽度的⼀半,以及⾼度恰巧就是窗⼝的左上⻆QPoint volumeLeftTop =  point - QPoint(volumeTool->width()/2, volumeTool->height());// 微调窗⼝位置volumeLeftTop.setY(volumeLeftTop.y()+30);volumeLeftTop.setX(volumeLeftTop.x()+15);// 3. 移动窗⼝位置volumeTool->move(volumeLeftTop);// 4. 将窗⼝显⽰出来volumeTool->show();
}

3.6.5、绘制三角

由于 Qt 中并未给出三角控件,因此三角需要手动绘制,故在 VolumeTool 类中重写 paintEvent 事件函数。

// volumetool.h中新增
void paintEvent(QPaintEvent *event);// volumetool.cpp中新增
#include <QPainter>void VolumeTool::paintEvent(QPaintEvent *event)
{(void)event;// 1. 创建绘图对象QPainter painter(this);// 2. 设置抗锯⻮painter.setRenderHint(QPainter::Antialiasing, true);// 3. 设置画笔// 没有画笔时:画出来的图形就没有边框和轮廓线painter.setPen(Qt::NoPen);// 4. 设置画刷颜⾊painter.setBrush(Qt::white);// 创建⼀个三⻆形QPolygon polygon;polygon.append(QPoint(30, 300));polygon.append(QPoint(70, 300));polygon.append(QPoint(50, 320));// 绘制三⻆形painter.drawPolygon(polygon);
}

四、结束语

今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

相关文章:

  • 文件上传/读取/包含漏洞技术说明
  • 大模型——GraphRAG基于知识图谱+大模型技术构建的AI知识库系统
  • 第1.3讲、什么是 Attention?——从点菜说起 [特殊字符]️
  • LeetCode 1781. 所有子字符串美丽值之和 题解
  • ultralytics框架进行RT-DETR目标检测训练
  • EASM外部攻击面管理平台
  • Relay算子注册
  • 7.9/Q1,Charls最新文章解读
  • Dagger中编译import报找不到ProvideClientFactory,initialize中ProvideClientFactory爆红
  • 猿人学刷题系列(第一届比赛)——第一题
  • 技术对暴力的削弱
  • 【C/C++】构造函数与析构函数
  • 强化学习+多模态 从理论到实战
  • Python Cookbook-7.4 对类和实例使用 cPickle 模块
  • 论软件的可靠性设计
  • 排序算法——堆排序
  • 【PPT制作利器】DeepSeek + Kimi生成一个初始的PPT文件
  • 椭球面长度计算的两种公式及投影选择
  • MySQL 窗口函数入门到精通
  • Coding Practice,48天强训(30)
  • 美国与胡塞武装达成停火协议,美伊相向而行?
  • 湖北奥莱斯轮胎公司逃避监管排放大气污染物被罚25万元
  • 关税风暴下,3G资本拟94亿美元私有化美国鞋履巨头斯凯奇,溢价30%
  • 经济日报:落实落细更加积极的财政政策
  • 五一多城楼市火热:北京新房网签量同比翻倍,上海热门楼盘认购接连触发积分
  • “注胶肉”或已泛滥?这几种肉,再爱吃也要管住嘴