具体代码见:https://gitee.com/Suinnnnnn/MusicPlayer
文章目录
- 0. 预备
- 1. 界面
- 1.1 各部位长度
- 1.2 ui文件
- 1.3 窗口前置设置
- 1.4 设置QSS
- 2. 自定义控件
- 2.1 按钮
- 2.2 推荐页面
- 2.3 CommonPage
- 2.4 滑杆
- 3. 音乐管理
- 4. 歌词界面
- 4.1 ui文件
- 4.2 LrcPage.h文件
- 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
如下
#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)
{// 如果按下左键,记录dragPosif (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
#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
#ifndef RECOMMENDBOX_H
#define RECOMMENDBOX_H#include <qjsonarray.h>
#include <QWidget>namespace Ui
{
class RecommendBox;
}class RecommendBox : public QWidget
{Q_OBJECTpublic: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
如下
#ifndef RECBOXITEM_H
#define RECBOXITEM_H#include <QPropertyAnimation>
#include <QWidget>namespace Ui
{
class RecBoxItem;
}class RecBoxItem : public QWidget
{Q_OBJECTpublic: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
(目前)
#ifndef COMMONPAGE_H
#define COMMONPAGE_H#include <QFrame>namespace Ui
{
class CommonPage;
}class CommonPage : public QFrame
{Q_OBJECTpublic: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
如下
#ifndef COMMONPAGEITEM_H
#define COMMONPAGEITEM_H
#include <QFrame>namespace Ui
{
class CommonPageItem;
}class CommonPageItem : public QFrame
{Q_OBJECTpublic: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
#ifndef COMMONSLIDER_H
#define COMMONSLIDER_H#include <QFrame>namespace Ui
{
class CommonSlider;
}/* 愿意是想让进度条和音量条都用该类,后来发现不如再写一个类。此类仅用于进度条 */
class CommonSlider : public QFrame
{Q_OBJECTpublic: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
#ifndef VOLUMEBOX_H
#define VOLUMEBOX_H#include <QFrame>namespace Ui
{
class VolumeBox;
}class VolumeBox : public QFrame
{Q_OBJECTpublic:explicit VolumeBox(QWidget* parent = nullptr);~VolumeBox();
private:Ui::VolumeBox* ui;
};#endif // VOLUMEBOX_H
在music.cpp
中给音量按钮添加事件过滤器
bool Music::eventFilter(QObject* obj, QEvent* event)
{if (obj == ui->play_volume_btn || obj == volume_box) {if (event->type() == QEvent::Enter) {// 鼠标进入时显示 volume_boxQPoint 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
,储存音乐信息
#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; // 歌曲的URLQString 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
#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; // 歌曲的URLQString 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
中增加处理点击爱心按钮的槽函数
void handle_item_like_btn_clicked(bool is_like, const QUuid& uuid);
4. 歌词界面
解析.lrc文件
4.1 ui文件
4.2 LrcPage.h文件
#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_OBJECTpublic: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
类
#ifndef CUSTOMPLAYLIST_H
#define CUSTOMPLAYLIST_H#include <QList>
#include <QMediaPlayer>
#include <QObject>
#include <QRandomGenerator>
#include <QUrl>class CustomPlaylist : public QObject
{Q_OBJECTpublic: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; // 关联的 QMediaPlayerQList<int> m_randomOrder; // 随机播放顺序缓存void updateRandomOrder(); // 更新随机播放顺序int getNextIndex() const; // 根据模式获取下一个索引int getPreviousIndex() const; // 根据模式获取上一个索引
};
#endif // CUSTOMPLAYLIST_H
使用QSQLITE数据库,用于持久化操作,下面是music.h
文件
#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_NAMESPACEclass Music : public QWidget
{Q_OBJECTpublic: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