【实战项目】简易版的 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 行,内部有4个推荐元素;下方显示 2 行,每行有 4 个推荐元素
- 左右两侧⼀个按钮,点击后推荐内容会更换下一批,不停点击会循环推荐
- 当鼠标悬停在推荐元素上时,推荐元素会向上移动,当鼠标离开时,又回到原位置
- 当鼠标悬停在推荐元素上时,同时会出现小手图标,说明该推荐元素具有点击功能
我喜欢、本地下载、最近播放类似下图:
这三个Page中布局、控件都是相同的,只是填充的数据不⼀样。每个Page中包含了多个控件,大致如下:
- ① QLabel:类型说明
- ② QLabel:图⽚显⽰
- ③ QButton:播放全部按钮
- ④ ⼀组QLabel说明:⾳乐、歌⼿、专辑
- ⑤ QListWidget:播放列表
歌词页面:解析当前正在播放音乐的歌词,同步显示在界面上。
显示内容分为:歌曲信息、歌词部分、左上方收起隐藏按钮。
- 歌曲信息由歌曲名称(QLabel)和歌手名称(QLabel)构成
- 歌词部分展示当前在唱歌词(QLabel)和在唱部分前三行和后三行歌词(QLabel)展示,当前播放
- 歌词突出显示
- 点击收起按钮后,该页面会以动画的方式收起
说明:
- 当歌曲有LRC歌词时,播放时歌词会随播放时间自动调整;歌曲没有LRC歌词时,歌词部分显示空字符。
- 以上对本项目的界面进行了简单的说明,大家先有个初步了解,接下来利用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 内部布局:
- 拖拽⼀个 Widget 到 bodyLeft ,将 objectName 修改为 leftBox ,背景颜⾊修改为:background-color:pink.
- 拖拽 Vertical Spacer 到 bodyLeft.
- 选中 leftBox ,将 minmumSize 和 maxmumSize 的高度修改为 400.
- 选中 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);
}
运行后,发现有以下两个问题:
- 窗口无标题栏,找不到关闭按钮,导致窗口无法关闭
- 窗口无法拖拽
关闭窗口,可以先将光标放在任务栏中当前应用程序图标上,弹出的框中选择关闭,后序会实现关闭功能:
主界面无法拖动,此时只需要处理下鼠标单击 (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 类,具体步骤如下:
- 创建 QGraphicsDropShadowEffect 类对象
- 设置阴影的属性。比如:设置阴影的偏移、颜色、圆角等
- 阴影设置到具体对象上
在 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);
}
四、结束语
今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。