具体代码见:https://gitee.com/Suinnnnnn/MusicPlayer
文章目录
- [0. 预备](#0. 预备)
- [1. 界面](#1. 界面)
-
- [1.1 各部位长度](#1.1 各部位长度)
- [1.2 ui文件](#1.2 ui文件)
- [1.3 窗口前置设置](#1.3 窗口前置设置)
- [1.4 设置QSS](#1.4 设置QSS)
- [2. 自定义控件](#2. 自定义控件)
-
- [2.1 按钮](#2.1 按钮)
- [2.2 推荐页面](#2.2 推荐页面)
- [2.3 CommonPage](#2.3 CommonPage)
- [2.4 滑杆](#2.4 滑杆)
- [3. 音乐管理](#3. 音乐管理)
- [4. 歌词界面](#4. 歌词界面)
-
- [4.1 ui文件](#4.1 ui文件)
- [4.2 LrcPage.h文件](#4.2 LrcPage.h文件)
- [5. 音乐播放控制 + 持久化控制](#5. 音乐播放控制 + 持久化控制)
0. 预备
各个类的作用:
ButtonForm
: 自定义了按钮,其包含了图片,文字和动画效果
CommonPage
:自定义页面,被"我的"之后的三个页面使用
CommonPageItem
:歌曲的每一行就是一个该类的对象,包括编号,歌曲名,专辑名,时间,是否喜欢
CommonSlider
:音乐进度条使用,表明当前播放到哪里,支持seek功能
CustomPlaylist
:简单实现了在Qt6中删除的QMediaPlaylist
LrcPage
:歌词页面
Music
:用户直接交互的页面
MusicInfo
:保存歌曲信息的类
MusicList
:组织MusicInfo
的类
RecBoxItem
:RecommendBox
中的一个个单元
RecommendBox
:推荐界面的简单实现
VolumBox
:音量控制条,支持seek功能
1. 界面
1.1 各部位长度

1.2 ui文件


1.3 窗口前置设置
music.cpp
如下
cpp
#include "music.h"
#include <QGraphicsDropShadowEffect>
#include <QMouseEvent>
#include "ui_music.h"
Music::Music(QWidget* parent)
: QWidget(parent)
, ui(new Ui::Music)
{
ui->setupUi(this);
initUI();
}
void Music::initUI()
{
// 设置无边框
this->setWindowFlag(Qt::FramelessWindowHint);
// 设置窗体透明,不然看不到阴影效果
this->setAttribute(Qt::WA_TranslucentBackground);
// 添加阴影效果
QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect(this);
shadowEffect->setOffset(0, 0); // 设置阴影距离
shadowEffect->setColor(QColor(0, 0, 0)); // 设置阴影颜色
shadowEffect->setBlurRadius(100); // 设置阴影模糊半径
this->setGraphicsEffect(shadowEffect); // 给 QWidget设置阴影
}
void Music::mousePressEvent(QMouseEvent* event)
{
// 如果按下左键,记录dragPos
if (event->button() == Qt::LeftButton) {
QPoint p1 = event->globalPosition().toPoint();
QPoint p2 = geometry().topLeft();
this->dragPos = p1 - p2;
// qDebug() << dragPos;
return;
}
// 其余事件让父类处理
QWidget::mousePressEvent(event);
}
void Music::mouseMoveEvent(QMouseEvent* event)
{
// 如果按下左键并移动,那么就让窗口移动
if (event->buttons() == Qt::LeftButton) {
QPoint p1 = event->globalPosition().toPoint();
QPoint p2 = this->dragPos;
this->move(p1 - p2);
// qDebug() << "dragPos: " << p2 << "\nglobalPos: " << p1;
return;
}
// 其余事件让父类处理
QWidget::mouseMoveEvent(event);
}
Music::~Music()
{
// qDebug() << "Music::~Music() called";
delete ui;
}
void Music::on_close_btn_clicked()
{
// 关闭窗口
this->close();
}
1.4 设置QSS
在widget.ui
中删除部分空间的背景颜色,设置QSS。
2. 自定义控件
2.1 按钮
自定义一个ButtonForm
,由几个QWidget
和QLabel
组成
buttonform.h
cpp
#ifndef BUTTONFORM_H
#define BUTTONFORM_H
#include <QFrame>
#include <QPropertyAnimation>
namespace Ui
{
class ButtonForm;
}
class ButtonForm : public QFrame
{
Q_OBJECT
public:
explicit ButtonForm(QWidget* parent = nullptr);
void set_id_and_icon_and_text(int id, const QString& icon, const QString& text);
void clear_backgrond_color() const;
int get_page_id() const;
void show_animation();
void hide_animation();
~ButtonForm();
protected:
void mousePressEvent(QMouseEvent* event);
private:
void set_animation_and_start(QPropertyAnimation* animation, int duration,
QRect rect1, QRect rect2, QRect rect3, int loopCount);
Ui::ButtonForm* ui;
int pageID; // 当前页面的ID, 用于翻页
QPropertyAnimation* line1_animation; // 动画
QPropertyAnimation* line2_animation;
QPropertyAnimation* line3_animation;
QPropertyAnimation* line4_animation;
QPropertyAnimation* line5_animation;
signals:
void clicked(int pageID);
};
#endif // BUTTONFORM_H
接着将music.ui
中的左侧QWidget
替换为ButtonForm
运行结果如下,点击不同的按钮可以跳转到不同的QWidget
,且按钮有动画效果

2.2 推荐页面
在music.ui
中的recommend_page
下添加一个QScrollArea
类型的scrollArea
,里面有三个QLabel和两个RecommendBox

RecommendBox.ui
如下

RecommendBox.h
cpp
#ifndef RECOMMENDBOX_H
#define RECOMMENDBOX_H
#include <qjsonarray.h>
#include <QWidget>
namespace Ui
{
class RecommendBox;
}
class RecommendBox : public QWidget
{
Q_OBJECT
public:
explicit RecommendBox(QWidget* parent = nullptr);
void init_rec_box(QJsonArray pics);
void set_col_and_row(int row, int col);
~RecommendBox();
private slots:
void on_left_btn_clicked();
void on_right_btn_clicked();
private:
void add_rec_item();
void del_rec_item();
Ui::RecommendBox* ui;
int row = 2;
int column = 5;
int current_group = 0; // 当前是第几组
int group_count; // 一共有多少组
QJsonArray pics_path_and_text;
};
#endif // RECOMMENDBOX_H
RecBoxItem.ui
如下

RecBoxItem.h
如下
cpp
#ifndef RECBOXITEM_H
#define RECBOXITEM_H
#include <QPropertyAnimation>
#include <QWidget>
namespace Ui
{
class RecBoxItem;
}
class RecBoxItem : public QWidget
{
Q_OBJECT
public:
explicit RecBoxItem(QWidget* parent = nullptr);
bool eventFilter(QObject* watched, QEvent* event);
void set_icon_and_text(const QString& icon, const QString& text);
~RecBoxItem();
private:
Ui::RecBoxItem* ui;
std::function<void(QPropertyAnimation*, int, QRect, QRect, QRect, int)> set_animation_and_start;
};
#endif // RECBOXITEM_H
运行结果如下,支持换页,而且选中图片会有动画效果

2.3 CommonPage
由于需要上传文件,所以现在把之前的我的播客
改为上传音乐

CommonPage
被播客, 我喜欢, 最近播放, 上传音乐所使用。
下面是CommonPage.ui

下面是CommonPage.h
(目前)
cpp
#ifndef COMMONPAGE_H
#define COMMONPAGE_H
#include <QFrame>
namespace Ui
{
class CommonPage;
}
class CommonPage : public QFrame
{
Q_OBJECT
public:
explicit CommonPage(QWidget* parent = nullptr);
void init_my_like();
void init_podcast();
void init_recent_play();
void init_upload_music();
~CommonPage();
private:
void set_podcast();
void reset_up_widget(const QString& title = "xxx");
Ui::CommonPage* ui;
};
#endif // COMMONPAGE_H
CommonPage
使用了CommonPageItem
,下面是CommonPageItem.ui

CommonPageItem.h
如下
cpp
#ifndef COMMONPAGEITEM_H
#define COMMONPAGEITEM_H
#include <QFrame>
namespace Ui
{
class CommonPageItem;
}
class CommonPageItem : public QFrame
{
Q_OBJECT
public:
explicit CommonPageItem(QWidget* parent = nullptr);
const QRect& get_page_item_geometry() const;
void set_num(int num);
void enterEvent(QEnterEvent* event);
void leaveEvent(QEvent* event);
void mouseDoubleClickEvent(QMouseEvent* event);
~CommonPageItem();
private:
void set_background_color(const QString& color);
bool is_played() const;
int play_num = -1;
Ui::CommonPageItem* ui;
};
#endif // COMMONPAGEITEM_H
2.4 滑杆
CommonSlider.ui
,音乐进度条使用

CommonSlider.h
cpp
#ifndef COMMONSLIDER_H
#define COMMONSLIDER_H
#include <QFrame>
namespace Ui
{
class CommonSlider;
}
/* 愿意是想让进度条和音量条都用该类,后来发现不如再写一个类。此类仅用于进度条 */
class CommonSlider : public QFrame
{
Q_OBJECT
public:
explicit CommonSlider(QWidget* parent = nullptr);
void init_progress_bar();
void init_volume_btn();
~CommonSlider();
private:
Ui::CommonSlider* ui;
};
#endif // COMMONSLIDER_H
VolumeBox.ui
,音量按钮使用

VolumeBox.h
cpp
#ifndef VOLUMEBOX_H
#define VOLUMEBOX_H
#include <QFrame>
namespace Ui
{
class VolumeBox;
}
class VolumeBox : public QFrame
{
Q_OBJECT
public:
explicit VolumeBox(QWidget* parent = nullptr);
~VolumeBox();
private:
Ui::VolumeBox* ui;
};
#endif // VOLUMEBOX_H
在music.cpp
中给音量按钮添加事件过滤器
cpp
bool Music::eventFilter(QObject* obj, QEvent* event)
{
if (obj == ui->play_volume_btn || obj == volume_box) {
if (event->type() == QEvent::Enter) {
// 鼠标进入时显示 volume_box
QPoint btnPos = ui->play_volume_btn->mapToGlobal(QPoint(0, 0)); // 获取全局坐标
QPoint targetPos = btnPos - QPoint(10, volume_box->height()); // 按钮上方
volume_box->move(targetPos);
volume_box->show();
} else if (event->type() == QEvent::Leave) {
// 鼠标离开时隐藏 VolumeBox (如果超过了300ms)
hide_timer->start(300);
}
}
return QWidget::eventFilter(obj, event);
}
3. 音乐管理
设置此按钮的槽函数

点击后需要添加我们选中的文件,使用QFileDialog
。使用MusicInfo
类保存所有音乐的信息。使用MusicList
类来管理这些Music
MusicInfo.h
,储存音乐信息
cpp
#ifndef MUSICINFO_H
#define MUSICINFO_H
#include <QString>
#include <QUrl>
#include <QUuid>
class MusicInfo
{
public:
MusicInfo(const QUrl& url);
// 禁止拷贝(根据需要实现移动语义)
MusicInfo(const MusicInfo&) = delete;
MusicInfo& operator=(const MusicInfo&) = delete;
/* 必须声明移动构造函数,作用如下
* 1. 明确告知编译器生成移动构造函数
* 2. 允许容器使用移动语义操作对象
* 3. 保留类的不可拷贝特性
* */
MusicInfo(MusicInfo&&) = default;
MusicInfo& operator=(MusicInfo&&) = default;
bool is_valid();
void parse_metadata();
QUuid getUuid() const;
bool getIs_like() const;
void setIs_like(bool newIs_like);
bool getIs_recent_play() const;
void setIs_recent_play(bool newIs_recent_play);
bool getIs_valid_flag() const;
void setIs_valid_flag(bool newIs_valid_flag);
QString getMusic_name() const;
QString getAlbum_name() const;
qint64 getMusic_time() const;
private:
void check_valid();
QUuid uuid; // uuid, 防止重复
QUrl url; // 歌曲的URL
QString music_name; // 音乐名称
QString album_name; // 专辑名称
qint64 music_time; // 音乐持续时间(ms)
bool is_like = false; // 是否喜欢
bool is_recent_play = false; // 是否是最近播放
bool is_valid_flag = false; // 是否是音频文件
};
#endif // MUSICINFO_H
MusicList.h
,管理一个个MusicInfo
cpp
#ifndef MUSICINFO_H
#define MUSICINFO_H
#include <QString>
#include <QUrl>
#include <QUuid>
class MusicInfo
{
public:
MusicInfo(const QUrl& url);
// 禁止拷贝(根据需要实现移动语义)
MusicInfo(const MusicInfo&) = delete;
MusicInfo& operator=(const MusicInfo&) = delete;
/* 必须声明移动构造函数,作用如下
* 1. 明确告知编译器生成移动构造函数
* 2. 允许容器使用移动语义操作对象
* 3. 保留类的不可拷贝特性
* */
MusicInfo(MusicInfo&&) = default;
MusicInfo& operator=(MusicInfo&&) = default;
bool is_valid();
void parse_metadata();
QUuid getUuid() const;
bool getIs_like() const;
void setIs_like(bool newIs_like);
bool getIs_recent_play() const;
void setIs_recent_play(bool newIs_recent_play);
bool getIs_valid_flag() const;
void setIs_valid_flag(bool newIs_valid_flag);
QString getMusic_name() const;
QString getAlbum_name() const;
qint64 getMusic_time() const;
private:
void check_valid();
QUuid uuid; // uuid, 防止重复
QUrl url; // 歌曲的URL
QString music_name; // 音乐名称
QString album_name; // 专辑名称
qint64 music_time; // 音乐持续时间(ms)
bool is_like = false; // 是否喜欢
bool is_recent_play = false; // 是否是最近播放
bool is_valid_flag = false; // 是否是音频文件
};
#endif // MUSICINFO_H
Music.h
中增加处理点击爱心按钮的槽函数
cpp
void handle_item_like_btn_clicked(bool is_like, const QUuid& uuid);
4. 歌词界面
解析.lrc文件
4.1 ui文件

4.2 LrcPage.h文件
cpp
#ifndef LRCPAGE_H
#define LRCPAGE_H
#include <QFrame>
#include <QPropertyAnimation>
namespace Ui
{
class LrcPage;
}
// 歌词的一行
struct LyricLine {
LyricLine(qint64 time_p, QString text_p)
: time(time_p), text(text_p)
{
}
qint64 time; // 时间(ms)
QString text; // 歌词
};
class LrcPage : public QFrame
{
Q_OBJECT
public:
explicit LrcPage(QWidget* parent = nullptr);
bool parse_lrc_file(const QString& lrc_filename);
void push_line_to_lrc_vector(qint64 time, QString text);
QString get_lrc_filename_by_url(const QUrl& url);
void set_ui_label(qint64 time);
void set_name_and_singer(const QString& music, const QString& singer);
void clear_lyric_lines();
~LrcPage();
private slots:
void on_quit_clicked();
private:
int get_lyric_lines_index_by_time(qint64 time);
QString get_lyric_lines_line_by_index(int index);
Ui::LrcPage* ui;
QPropertyAnimation* lrc_animation; // 歌词界面隐藏动画
QVector<LyricLine> lyric_lines; // 歌词的所有内容,由一行行的结构体组成
};
#endif // LRCPAGE_H
5. 音乐播放控制 + 持久化控制
使用QMediaPlayer
类,由于Qt6中删除了QPlayList
类,所以我自定义了一个CustomPlayList
类
cpp
#ifndef CUSTOMPLAYLIST_H
#define CUSTOMPLAYLIST_H
#include <QList>
#include <QMediaPlayer>
#include <QObject>
#include <QRandomGenerator>
#include <QUrl>
class CustomPlaylist : public QObject
{
Q_OBJECT
public:
enum PlaybackMode {
Sequential, // 顺序播放
SingleLoop, // 单曲循环播放
Random // 随机播放
};
Q_ENUM(PlaybackMode)
explicit CustomPlaylist(QMediaPlayer* player, QObject* parent = nullptr);
// 基本操作
void addMedia(const QUrl& url);
void removeMedia(int index);
void clear();
void next();
void previous();
void setCurrentIndex(int index);
// 获取信息
int currentIndex() const;
QUrl currentMedia() const;
int mediaCount() const;
// 播放模式
PlaybackMode playbackMode() const;
void setPlaybackMode(PlaybackMode mode);
signals:
void currentIndexChanged(int index);
void mediaAdded(int index);
void mediaRemoved(int index);
void playbackModeChanged(PlaybackMode mode);
private slots:
void handleMediaStatusChanged(QMediaPlayer::MediaStatus status);
private:
QList<QUrl> m_mediaList; // 存储媒体项
int m_currentIndex = -1; // 当前播放索引
PlaybackMode m_playbackMode = Sequential;
QMediaPlayer* m_player; // 关联的 QMediaPlayer
QList<int> m_randomOrder; // 随机播放顺序缓存
void updateRandomOrder(); // 更新随机播放顺序
int getNextIndex() const; // 根据模式获取下一个索引
int getPreviousIndex() const; // 根据模式获取上一个索引
};
#endif // CUSTOMPLAYLIST_H
使用QSQLITE数据库,用于持久化操作,下面是music.h
文件
cpp
#ifndef MUSIC_H
#define MUSIC_H
#include <QAudioOutput>
#include <QJsonArray>
#include <QMediaPlayer>
#include <QPropertyAnimation>
#include <QSqlDatabase>
#include <QWidget>
#include "CommonPage.h"
#include "CustomPlaylist.h"
#include "LrcPage.h"
#include "VolumeBox.h"
QT_BEGIN_NAMESPACE
namespace Ui
{
class Music;
}
QT_END_NAMESPACE
class Music : public QWidget
{
Q_OBJECT
public:
Music(QWidget* parent = nullptr);
void initUI();
void initPlayer();
void initStaticToolTip();
void initSqlLite();
void initMusicList();
void connect_signals_and_slots();
void mousePressEvent(QMouseEvent* event);
void mouseMoveEvent(QMouseEvent* event);
bool eventFilter(QObject* obj, QEvent* event);
void play_music_from_index(CommonPage* page, int index);
QJsonArray get_random_recommend_pic() const;
~Music();
private slots:
// 形如on_xxx是ui文件生成的, 其余是自己写的
void handle_button_form_clicked(int pageID);
void handle_item_like_btn_clicked(bool is_like, const QUuid& uuid);
void handle_upload_music();
void handle_play_state_changed(QMediaPlayer::PlaybackState new_state);
void handle_play_mode_changed(CustomPlaylist::PlaybackMode mode);
void handle_play_all_music_btn_clicked(CommonPage* page);
void handle_double_clicked_song(CommonPage* page, int index);
void handle_current_index_changed(int new_index);
void handle_volume_changed(int volume);
void handle_duration_changed(qint64 duration);
void handle_position_changed(qint64 duration);
void handle_slider_pos_changed(double ratio);
void handle_meta_data_changed();
void on_close_btn_clicked();
void on_stop_btn_clicked();
void on_next_song_btn_clicked();
void on_prev_song_btn_clicked();
void on_play_mode_btn_clicked();
void on_play_volume_btn_clicked();
void on_my_like_btn_clicked();
void on_lrc_btn_clicked();
void on_music_pic_btn_clicked();
void on_min_btn_clicked();
private:
void hide_volume_box();
static QString transform_million_second(qint64 msecond);
void set_like_btn_icon(bool is_like);
MusicList::const_iterator get_now_play_music_info_by_index(int index);
void quit_progress();
private:
Ui::Music* ui;
// 由于窗口没有了默认边框,所以要想移动,需要有该属性,其值为 全局的鼠标位置-widget左上位置
QPoint dragPos;
VolumeBox* volume_box;
QTimer* hide_timer;
QMediaPlayer* player;
CustomPlaylist* play_list;
QAudioOutput* audio_output;
CommonPage* play_page; // 当前播放
bool is_mute = false; // 是否静音, false表示没被静音
qint64 total_duration; // 当前音乐的总时长
LrcPage* lrc_page; // 歌词页面
QPropertyAnimation* lrc_animation; // 歌词界面显示动画
QSqlDatabase db; // 数据库驱动
};
#endif // MUSIC_H