├── .gitignore ├── HaoMusic.pro ├── LICENSE ├── README.md ├── addsonglistpage.cpp ├── addsonglistpage.h ├── addsonglistpage.ui ├── coveritem.cpp ├── coveritem.h ├── coveritem.ui ├── customitem.cpp ├── customitem.h ├── customitem.ui ├── customslider.cpp ├── customslider.h ├── ellipsislabel.cpp ├── ellipsislabel.h ├── gaussianblur.cpp ├── gaussianblur.h ├── haomusic.cpp ├── haomusic.h ├── haomusic.ui ├── icon ├── Max2normal.svg ├── Music.svg ├── MusicPlayer.ico ├── Player, pause.svg ├── Player, play.svg ├── add.svg ├── close.svg ├── downArow_black.svg ├── downArow_gray.svg ├── folder-open.svg ├── home.svg ├── loading.gif ├── max.svg ├── min.svg ├── music_last.svg ├── music_next.svg ├── noVolume.svg ├── order-play-fill.svg ├── pkq.jpg ├── player-list-add.svg ├── player-pause.svg ├── player-skip-back.svg ├── player-skip-forward.svg ├── player_paly.svg ├── queue_music.svg ├── random.svg ├── repeat-one-line.svg ├── repeat.svg ├── rounded .svg ├── search.svg └── volume.svg ├── iconlist.cpp ├── iconlist.h ├── lrcwidget.cpp ├── lrcwidget.h ├── main.cpp ├── music.cpp ├── music.h ├── musicdb.cpp ├── musicdb.h ├── musiclist.cpp ├── musiclist.h ├── mybottombar.cpp ├── mybottombar.h ├── myhttp.cpp ├── myhttp.h ├── mylabel.cpp ├── mylabel.h ├── mylineedit.cpp ├── mylineedit.h ├── mylistwidget.cpp ├── mylistwidget.h ├── mymediaplaylist.cpp ├── mymediaplaylist.h ├── res.qrc ├── searchtipslist.cpp ├── searchtipslist.h ├── shadowwidget.cpp ├── shadowwidget.h ├── static ├── lrc_Page.png ├── lrc_Page_gaussionBackground.png ├── menu_buttonRight.png └── searchResult_Page.png ├── style.css ├── switchanimation.cpp ├── switchanimation.h ├── threaddownloader.cpp ├── threaddownloader.h └── 更新日志.md /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | CMakeLists.txt.user* 41 | 42 | # xemacs temporary files 43 | *.flc 44 | 45 | # Vim temporary files 46 | .*.swp 47 | 48 | # Visual Studio generated files 49 | *.ib_pdb_index 50 | *.idb 51 | *.ilk 52 | *.pdb 53 | *.sln 54 | *.suo 55 | *.vcproj 56 | *vcproj.*.*.user 57 | *.ncb 58 | *.sdf 59 | *.opensdf 60 | *.vcxproj 61 | *vcxproj.* 62 | 63 | # MinGW generated files 64 | *.Debug 65 | *.Release 66 | 67 | # Python byte code 68 | *.pyc 69 | 70 | # Binaries 71 | # -------- 72 | *.dll 73 | *.exe 74 | 75 | -------------------------------------------------------------------------------- /HaoMusic.pro: -------------------------------------------------------------------------------- 1 | QT += core gui 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets multimedia network concurrent sql 4 | 5 | CONFIG += c++17 6 | 7 | # You can make your code fail to compile if it uses deprecated APIs. 8 | # In order to do so, uncomment the following line. 9 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 10 | 11 | #程序版本 12 | VERSION = 1.0 13 | #程序图标 14 | RC_ICONS = icon/MusicPlayer.ico 15 | #产品名称 16 | QMAKE_TARGET_PRODUCT = MusicPlayer 17 | #版权所有 18 | QMAKE_TARGET_COPYRIGHT = hao 19 | #文件说明 20 | QMAKE_TARGET_DESCRIPTION = HaoMusic 21 | 22 | #禁用qdebug打印输出 23 | #DEFINES += QT_NO_DEBUG_OUTPUT 24 | 25 | #关闭编译警告提示 眼不见为净 26 | #CONFIG += warn_off 27 | 28 | #指定编译生成的文件到temp目录 分门别类存储 29 | #MOC_DIR = temp/moc 30 | #RCC_DIR = temp/rcc 31 | #UI_DIR = temp/ui 32 | #OBJECTS_DIR = temp/obj 33 | 34 | #指定编译生成的可执行文件到bin目录 35 | #DESTDIR = bin 36 | 37 | SOURCES += \ 38 | addsonglistpage.cpp \ 39 | coveritem.cpp \ 40 | customitem.cpp \ 41 | customslider.cpp \ 42 | ellipsislabel.cpp \ 43 | gaussianblur.cpp \ 44 | iconlist.cpp \ 45 | lrcwidget.cpp \ 46 | main.cpp \ 47 | haomusic.cpp \ 48 | music.cpp \ 49 | musicdb.cpp \ 50 | musiclist.cpp \ 51 | mybottombar.cpp \ 52 | myhttp.cpp \ 53 | mylabel.cpp \ 54 | mylineedit.cpp \ 55 | mylistwidget.cpp \ 56 | mymediaplaylist.cpp \ 57 | searchtipslist.cpp \ 58 | shadowwidget.cpp \ 59 | switchanimation.cpp \ 60 | threaddownloader.cpp 61 | 62 | HEADERS += \ 63 | addsonglistpage.h \ 64 | coveritem.h \ 65 | customslider.h \ 66 | customitem.h \ 67 | ellipsislabel.h \ 68 | gaussianblur.h \ 69 | haomusic.h \ 70 | iconlist.h \ 71 | lrcwidget.h \ 72 | music.h \ 73 | musicdb.h \ 74 | musiclist.h \ 75 | mybottombar.h \ 76 | myhttp.h \ 77 | mylabel.h \ 78 | mylineedit.h \ 79 | mylistwidget.h \ 80 | mymediaplaylist.h \ 81 | searchtipslist.h \ 82 | shadowwidget.h \ 83 | switchanimation.h \ 84 | threaddownloader.h 85 | 86 | FORMS += \ 87 | addsonglistpage.ui \ 88 | coveritem.ui \ 89 | customitem.ui \ 90 | haomusic.ui 91 | 92 | # Default rules for deployment. 93 | qnx: target.path = /tmp/$${TARGET}/bin 94 | else: unix:!android: target.path = /opt/$${TARGET}/bin 95 | !isEmpty(target.path): INSTALLS += target 96 | 97 | QMAKE_CXXFLAGS += -Wno-unused-parameter 98 | 99 | RESOURCES += \ 100 | res.qrc 101 | 102 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 hhhyxy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MusicPlayer-in-Qt-Plus 2 | 3 | **基于Qt的音乐播放器的设计与实现** 4 | 5 | - 本项目旨在用Qt实现一个高颜值、高体验的音乐播放器 6 | - 目前使用的音源API来源于NeteaseCloudMusicApi的网易云音乐API,并部署在自己的服务器上(已挂,可以在myhttp.h中换上自己的音源地址),后续考虑添加其他音源。 7 | 8 | ## 项目运行 9 | 10 | ### 运行环境 11 | win10 12 | 13 | ### 编辑工具 14 | Qt Creator 9.0.1 15 | 16 | ### 编译器 17 | MinGW 8.1.0 18 | 19 | ## 功能和画饼 20 | 21 | - [x] 基本的UI框架 22 | - [x] 在线音乐搜索 23 | - [x] 搜索提示列表 24 | - [x] 音乐播放控制 25 | - [x] 歌词显示及滚动 26 | - [x] 我喜欢的音乐 27 | - [x] 本地数据存储 28 | - [x] 创建歌单功能 29 | - [ ] 云端数据存储 30 | 31 | ## 界面展示 32 | 33 | ### 搜索结果页面 34 | ![搜索结果页面](https://raw.githubusercontent.com/hhhyxy/MusicPlayer-in-Qt-Plus/main/static/searchResult_Page.png) 35 | 36 | ##### 功能介绍 37 | - 实现**圆角阴影**边框、拖动顶部可移动界面,双击可最大化,实现基本界面切换 38 | - 实现整体UI界面,并优化动态交互效果,实现界面切换、加载动画 39 | - 实现音乐搜索功能,搜索结果最大80条,使用**多线程**和**懒加载**方式提升页面加载速度 40 | - 实现**搜索提示列表**功能,并且实现列表展开/收回动画 41 | - 双击列表播放选中音乐,并可以在底部栏控制音乐切换、播放暂停、播放模式、音量 42 | - 点击底部栏空白处可切换到歌词界面,并且有上拉**动画** 43 | 44 | ### 歌词页面 45 | ![歌词页面](https://raw.githubusercontent.com/hhhyxy/MusicPlayer-in-Qt-Plus/main/static/lrc_Page_gaussionBackground.png) 46 | 47 | ##### 功能介绍 48 | - 实现歌词实时滚动,可使用鼠标上下**拖动**歌词 49 | - 点击歌词可实时更新音乐播放进度 50 | - 实现歌词页面**模糊背景**,采用高斯模糊的方式模糊专辑图片 51 | - 点击左上角下拉按钮回到主界面,并且有下拉**动画** 52 | 53 | ### 我喜欢的音乐界面 54 | 55 | - 界面样式与搜索结果页面一致 56 | - 可以添加歌曲到我喜欢的音乐 57 | 58 | ### 右键菜单 59 | ![右键菜单](https://raw.githubusercontent.com/hhhyxy/MusicPlayer-in-Qt-Plus/main/static/menu_buttonRight.png) 60 | 61 | ##### 功能介绍 62 | - 鼠标右键菜单可执行额外的操作 63 | 64 | ### 创建歌单功能 65 | 66 | ##### 功能介绍 67 | - 可以创建自己的歌单并向其中添加歌曲 68 | 69 | ## 灵感来源 70 | [YesPlayMusic](https://github.com/qier222/YesPlayMusic) 71 | 72 | [lx-music-desktop](https://github.com/lyswhut/lx-music-desktop) 73 | 74 | ## API来源 75 | [NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi) 76 | 77 | ## License 78 | [The MIT License (MIT)](https://github.com/hhhyxy/MusicPlayer-in-Qt/edit/main/LICENSE) 79 | -------------------------------------------------------------------------------- /addsonglistpage.cpp: -------------------------------------------------------------------------------- 1 | #include "addsonglistpage.h" 2 | #include "ui_addsonglistpage.h" 3 | #include "musiclist.h" 4 | #include 5 | #include 6 | AddSongListPage::AddSongListPage(QWidget *parent) : 7 | ShadowWidget(parent), 8 | ui(new Ui::AddSongListPage) 9 | { 10 | ui->setupUi(this); 11 | this->resize(600, 600); 12 | ui->lineEdit_listName->hide(); // 隐藏编辑框 13 | m_db = MusicDB::instance(); // 获取数据库连接 14 | // 搜索框失去焦点 15 | connect(ui->lineEdit_listName, &MyLineEdit::focusOut, [=] { 16 | ui->lineEdit_listName->hide(); 17 | ui->pushButton_newSongList->show(); 18 | }); 19 | // 按确认键创建歌单 20 | connect(ui->lineEdit_listName, &MyLineEdit::returnPressed, this, &AddSongListPage::createSonglist); 21 | } 22 | 23 | AddSongListPage::~AddSongListPage() 24 | { 25 | delete ui; 26 | } 27 | 28 | void AddSongListPage::init(Music& music) 29 | { 30 | this->setFocus(); 31 | // 移动到正中央 32 | this->move((parentWidget()->width() - this->width()) / 2, (parentWidget()->height() - this->height()) / 2); 33 | // 从数据库获取所有歌单 34 | QList songlists = m_db->queryList(); 35 | // 初始化歌单列表 36 | ui->listWidget_songlistWidget->clear(); 37 | this->addItems(songlists); 38 | this->music = music; 39 | this->show(); 40 | } 41 | 42 | // 关闭按钮 43 | void AddSongListPage::on_pushButton_close_clicked() 44 | { 45 | this->close(); 46 | } 47 | 48 | // 往列表添加歌单 49 | void AddSongListPage::addItems(QList& lists) 50 | { 51 | foreach (MusicList list, lists) { 52 | addItem(list); 53 | } 54 | } 55 | 56 | // 向listwidget添加歌单信息 57 | void AddSongListPage::addItem(MusicList& list) 58 | { 59 | QListWidgetItem *item = new QListWidgetItem(list.name()); 60 | item->setTextAlignment(Qt::AlignCenter); 61 | item->setData(Qt::UserRole, list.id()); 62 | ui->listWidget_songlistWidget->addItem(item); 63 | } 64 | 65 | // 新建歌单 66 | void AddSongListPage::on_pushButton_newSongList_clicked() 67 | { 68 | ui->pushButton_newSongList->hide(); 69 | ui->lineEdit_listName->clear(); 70 | ui->lineEdit_listName->show(); 71 | ui->lineEdit_listName->setFocus(); 72 | } 73 | 74 | // 添加歌曲到歌单 75 | void AddSongListPage::on_listWidget_songlistWidget_itemClicked(QListWidgetItem *item) 76 | { 77 | int id = item->data(Qt::UserRole).toInt(); 78 | emit add(id, music); 79 | this->close(); 80 | } 81 | 82 | // 创建歌单 83 | void AddSongListPage::createSonglist() 84 | { 85 | QString name = ui->lineEdit_listName->text(); 86 | // 歌单查重 87 | int cnt = ui->listWidget_songlistWidget->count(); 88 | for (int i = 0; i < cnt; i++) { 89 | if (name == ui->listWidget_songlistWidget->item(i)->text()) { 90 | ui->lineEdit_listName->clear(); 91 | qDebug() << __FILE__ << __LINE__ << "songlist is existed!"; 92 | return; 93 | } 94 | } 95 | // 隐藏编辑框,显示按钮 96 | ui->lineEdit_listName->hide(); 97 | ui->pushButton_newSongList->show(); 98 | // 创建歌单 99 | int id = m_db->insertList(MusicList(name)); 100 | MusicList list(id, name); 101 | addItem(list); 102 | emit create(id, name); 103 | } 104 | 105 | -------------------------------------------------------------------------------- /addsonglistpage.h: -------------------------------------------------------------------------------- 1 | #ifndef ADDSONGLISTPAGE_H 2 | #define ADDSONGLISTPAGE_H 3 | 4 | #include "shadowwidget.h" 5 | #include "musicdb.h" 6 | namespace Ui { 7 | class AddSongListPage; 8 | } 9 | /* 10 | * 添加到歌单的弹出界面 11 | */ 12 | class AddSongListPage : public ShadowWidget 13 | { 14 | Q_OBJECT 15 | 16 | public: 17 | explicit AddSongListPage(QWidget *parent = nullptr); 18 | ~AddSongListPage(); 19 | void init(Music& music); 20 | 21 | signals: 22 | // 创建歌单 23 | void create(int id, QString name); 24 | // 向歌单中添加歌曲 25 | void add(int id, Music music); 26 | 27 | private slots: 28 | void on_pushButton_close_clicked(); 29 | 30 | void on_pushButton_newSongList_clicked(); 31 | 32 | void on_listWidget_songlistWidget_itemClicked(QListWidgetItem *item); 33 | 34 | void createSonglist(); 35 | private: 36 | Ui::AddSongListPage *ui; 37 | MusicDB *m_db = nullptr; // 数据库连接 38 | 39 | Music music; 40 | 41 | private: 42 | void addItems(QList& lists); 43 | 44 | void addItem(MusicList& list); 45 | }; 46 | 47 | #endif // ADDSONGLISTPAGE_H 48 | -------------------------------------------------------------------------------- /addsonglistpage.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | AddSongListPage 4 | 5 | 6 | 7 | 0 8 | 0 9 | 670 10 | 482 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 0 27 | 0 28 | 29 | 30 | 31 | <h1>添加到歌单</h1> 32 | 33 | 34 | Qt::RichText 35 | 36 | 37 | false 38 | 39 | 40 | 41 | 42 | 43 | 44 | Qt::Horizontal 45 | 46 | 47 | 48 | 40 49 | 20 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 0 59 | 0 60 | 61 | 62 | 63 | 64 | 50 65 | 50 66 | 67 | 68 | 69 | 70 | 50 71 | 50 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | :/icon/close.svg:/icon/close.svg 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 新建歌单 89 | 90 | 91 | 新建歌单 92 | 93 | 94 | 95 | :/icon/add.svg:/icon/add.svg 96 | 97 | 98 | 99 | 18 100 | 18 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | QListView::Adjust 112 | 113 | 114 | Qt::AlignCenter 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | MyLineEdit 123 | QLineEdit 124 |
mylineedit.h
125 |
126 |
127 | 128 | 129 | 130 | 131 |
132 | -------------------------------------------------------------------------------- /coveritem.cpp: -------------------------------------------------------------------------------- 1 | #include "coveritem.h" 2 | #include "ui_coveritem.h" 3 | 4 | #include 5 | 6 | CoverItem::CoverItem(QWidget *parent) : 7 | QWidget(parent), 8 | ui(new Ui::CoverItem) 9 | { 10 | ui->setupUi(this); 11 | margin = this->layout()->margin(); 12 | this->setMouseTracking(true); 13 | } 14 | 15 | CoverItem::~CoverItem() 16 | { 17 | delete ui; 18 | } 19 | 20 | void CoverItem::setCover(QString url) 21 | { 22 | ui->label_cover->setRadiusPixmap(url); 23 | 24 | } 25 | 26 | void CoverItem::setCoverSize(QSize size) 27 | { 28 | ui->label_cover->resize(size); 29 | } 30 | 31 | void CoverItem::setName(QString name) 32 | { 33 | ui->label_name->setEllipsisText(name); 34 | } 35 | 36 | void CoverItem::setMargin(int mar) 37 | { 38 | ui->verticalLayout->setMargin(mar); 39 | margin = mar; 40 | } 41 | 42 | void CoverItem::normal() 43 | { 44 | ui->label_name->show(); 45 | } 46 | 47 | void CoverItem::zoomOut() 48 | { 49 | ui->label_name->hide(); 50 | } 51 | 52 | void CoverItem::mouseMoveEvent(QMouseEvent *event) 53 | { 54 | if (in) 55 | return; 56 | zoomOut(); 57 | in = true; 58 | QWidget::mouseMoveEvent(event); 59 | } 60 | 61 | void CoverItem::leaveEvent(QEvent *event) 62 | { 63 | if (!in) 64 | return; 65 | normal(); 66 | in = false; 67 | QWidget::leaveEvent(event); 68 | } 69 | 70 | -------------------------------------------------------------------------------- /coveritem.h: -------------------------------------------------------------------------------- 1 | #ifndef COVERITEM_H 2 | #define COVERITEM_H 3 | 4 | #include 5 | #include "mylabel.h" 6 | #include "ellipsislabel.h" 7 | namespace Ui { 8 | class CoverItem; 9 | } 10 | 11 | class CoverItem : public QWidget 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit CoverItem(QWidget *parent = nullptr); 17 | ~CoverItem(); 18 | void setCover(QString url); 19 | void setCoverSize(QSize size); 20 | void setName(QString name); 21 | void setMargin(int mar); 22 | void normal(); 23 | void zoomOut(); 24 | protected: 25 | void mouseMoveEvent(QMouseEvent *event); 26 | void leaveEvent(QEvent *event); 27 | private: 28 | Ui::CoverItem *ui; 29 | int margin; 30 | bool in = false; 31 | }; 32 | 33 | #endif // COVERITEM_H 34 | -------------------------------------------------------------------------------- /coveritem.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | CoverItem 4 | 5 | 6 | 7 | 0 8 | 0 9 | 382 10 | 347 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 20 19 | 20 | 21 | 20 22 | 23 | 24 | 20 25 | 26 | 27 | 20 28 | 29 | 30 | 31 | 32 | 33 | 0 34 | 0 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 0 47 | 0 48 | 49 | 50 | 51 | 52 | 53 | 54 | Qt::AlignCenter 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | MyLabel 63 | QLabel 64 |
mylabel.h
65 |
66 | 67 | EllipsisLabel 68 | QLabel 69 |
ellipsislabel.h
70 |
71 |
72 | 73 | 74 |
75 | -------------------------------------------------------------------------------- /customitem.cpp: -------------------------------------------------------------------------------- 1 | #include "customitem.h" 2 | #include "ui_customitem.h" 3 | #include "haomusic.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | CustomItem::CustomItem(Music music, QWidget *parent) : 16 | QWidget(parent), 17 | ui(new Ui::CustomItem) 18 | { 19 | ui->setupUi(this); 20 | // 无边框窗体 21 | setWindowFlags(Qt::FramelessWindowHint); 22 | // 初始化音乐信息 23 | this->music = music; 24 | // 字体溢出显示省略号 25 | ui->label_songName->setEllipsisText(music.getSongName()); 26 | ui->label_author->setEllipsisText(music.getAuthor()); 27 | ui->label_albumName->setEllipsisText(music.getAlbumName()); 28 | ui->label_duration->setText(music.getSongDuration()); 29 | // 显示专辑图片 30 | ui->label_albumPic->setRadiusPixmap(music.albumPicUrl()); 31 | // 自定义菜单 32 | connect(this, &CustomItem::customContextMenuRequested, this, &CustomItem::showMenu); 33 | } 34 | 35 | CustomItem::~CustomItem() 36 | { 37 | delete ui; 38 | } 39 | 40 | // 鼠标双击事件 41 | void CustomItem::mouseDoubleClickEvent(QMouseEvent *event) 42 | { 43 | if (Qt::LeftButton == event->button()) { 44 | emit myItemDoubleClicked(this); 45 | return; 46 | } 47 | QWidget::mouseDoubleClickEvent(event); 48 | } 49 | 50 | // 获取专辑图片 51 | QPixmap CustomItem::getAlbumPic() 52 | { 53 | return ui->label_albumPic->getImg(); 54 | } 55 | 56 | // 设置item类型 57 | void CustomItem::setItemType(int itemType) 58 | { 59 | this->itemType = itemType; 60 | } 61 | 62 | // 获取item类型 63 | int CustomItem::getItemType() const 64 | { 65 | return itemType; 66 | } 67 | 68 | // 获取item对应的音乐 69 | Music CustomItem::getMusic() const 70 | { 71 | return music; 72 | } 73 | 74 | // 改变字体颜色 75 | void CustomItem::changeFontColor(QString color) 76 | { 77 | QString qss; 78 | if (color == "black") { 79 | qss = "QLabel{color: black;};QLabel#label_author{color:#727272;};"; 80 | } 81 | else { 82 | qss = "color: blue;"; 83 | } 84 | this->setStyleSheet(qss); 85 | } 86 | 87 | // 初始化菜单 88 | void CustomItem::initMenu() 89 | { 90 | // 初始化菜单 91 | menu = new QMenu(this); 92 | // 给菜单添加子选项 93 | menu->addAction("播放音乐", [=] { 94 | emit menuClicked(this, CustomItem::PLAY); 95 | }); 96 | 97 | if (itemType != MyListWidget::FAVORITE) { 98 | menu->addAction("添加到我喜欢的音乐", [=] { 99 | emit menuClicked(this, CustomItem::ADDTOFAVORITE); 100 | }); 101 | } 102 | 103 | menu->addAction("添加到歌单", [=] { 104 | emit menuClicked(this, CustomItem::ADDTOSONGLIST); 105 | }); 106 | 107 | if (itemType == MyListWidget::FAVORITE) { 108 | menu->addAction("从我喜欢的音乐中删除", [=] { 109 | emit menuClicked(this, CustomItem::REMOVEFROMFAVORITE); 110 | }); 111 | } else if (itemType > MyListWidget::FAVORITE) { 112 | menu->addAction("从歌单中删除", [=] { 113 | emit menuClicked(this, CustomItem::REMOVEFROMSONGLIST); 114 | }); 115 | } 116 | 117 | // 菜单属性设置为无边框无阴影透明背景 118 | menu->setWindowFlags(menu->windowFlags() | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint); 119 | menu->setAttribute(Qt::WA_TranslucentBackground); 120 | // 给菜单设置阴影 121 | QGraphicsDropShadowEffect *effect = new QGraphicsDropShadowEffect(this); 122 | effect->setOffset(0,0); 123 | effect->setColor(QColor(150,150,150)); 124 | effect->setBlurRadius(10); 125 | menu->setGraphicsEffect(effect); 126 | } 127 | 128 | // 显示右键菜单 129 | void CustomItem::showMenu() 130 | { 131 | // 初始化菜单 132 | initMenu(); 133 | // 菜单显示在鼠标点击的位置 134 | menu->exec(QCursor::pos()); 135 | menu->deleteLater(); 136 | menu = nullptr; 137 | } 138 | -------------------------------------------------------------------------------- /customitem.h: -------------------------------------------------------------------------------- 1 | #ifndef CUSTOMITEM_H 2 | #define CUSTOMITEM_H 3 | 4 | #include "music.h" 5 | #include 6 | #include 7 | 8 | namespace Ui { 9 | class CustomItem; 10 | } 11 | /* 12 | * 搜索结果列表、歌单列表等列表的某一行 13 | */ 14 | class CustomItem : public QWidget 15 | { 16 | Q_OBJECT 17 | public: 18 | // item所在列表类型(枚举类型) 19 | // enum ListType{ 20 | // SEARCHRESULT, 21 | // LOCAL, 22 | // HISTORY, 23 | // FAVORITE 24 | // }; 25 | // 菜单项 26 | enum MenuType{ 27 | PLAY, 28 | ADDTOFAVORITE, 29 | REMOVEFROMFAVORITE, 30 | ADDTOSONGLIST, 31 | REMOVEFROMSONGLIST 32 | }; 33 | explicit CustomItem(Music music, QWidget *parent = nullptr); 34 | ~CustomItem(); 35 | // 获取item对应的音乐 36 | Music getMusic() const; 37 | // 改变字体颜色 38 | void changeFontColor(QString color); 39 | // 获取专辑图片 40 | QPixmap getAlbumPic(); 41 | // 设置item类型 42 | void setItemType(int itemType); 43 | // 获取item类型 44 | int getItemType() const; 45 | signals: 46 | // 菜单点击信号 47 | void menuClicked(CustomItem *item, int type); 48 | // item双击事件 49 | void myItemDoubleClicked(CustomItem *item); 50 | protected: 51 | // 双击事件 52 | void mouseDoubleClickEvent(QMouseEvent *event); 53 | private: 54 | Ui::CustomItem *ui; 55 | 56 | QMenu *menu = nullptr; // 菜单 57 | Music music; // item对应的音乐 58 | int itemType; // item类型 59 | // 初始化菜单 60 | void initMenu(); 61 | // 显示右键菜单 62 | void showMenu(); 63 | }; 64 | 65 | #endif // CUSTOMITEM_H 66 | -------------------------------------------------------------------------------- /customitem.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | CustomItem 4 | 5 | 6 | 7 | 0 8 | 0 9 | 855 10 | 80 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | 21 | 0 22 | 80 23 | 24 | 25 | 26 | 27 | 13534645 28 | 80 29 | 30 | 31 | 32 | 33 | PreferAntialias 34 | 35 | 36 | 37 | false 38 | 39 | 40 | Qt::CustomContextMenu 41 | 42 | 43 | Form 44 | 45 | 46 | 47 | 48 | 49 | 50 | 20 51 | 52 | 53 | 10 54 | 55 | 56 | 10 57 | 58 | 59 | 30 60 | 61 | 62 | 10 63 | 64 | 65 | 66 | 67 | 68 | 0 69 | 0 70 | 71 | 72 | 73 | 74 | QLayout::SetDefaultConstraint 75 | 76 | 77 | 0 78 | 79 | 80 | 0 81 | 82 | 83 | 0 84 | 85 | 86 | 0 87 | 88 | 89 | 90 | 91 | 92 | 0 93 | 0 94 | 95 | 96 | 97 | 98 | 60 99 | 60 100 | 101 | 102 | 103 | 104 | 60 105 | 60 106 | 107 | 108 | 109 | 110 | 40 111 | 40 112 | 113 | 114 | 115 | 116 | 117 | 118 | true 119 | 120 | 121 | 122 | 123 | 124 | 125 | 0 126 | 127 | 128 | QLayout::SetFixedSize 129 | 130 | 131 | 0 132 | 133 | 134 | 0 135 | 136 | 137 | 138 | 139 | 140 | 0 141 | 0 142 | 143 | 144 | 145 | 146 | 1726660 147 | 1726660 148 | 149 | 150 | 151 | 152 | true 153 | PreferAntialias 154 | 155 | 156 | 157 | 歌名 158 | 159 | 160 | Qt::PlainText 161 | 162 | 163 | true 164 | 165 | 166 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 167 | 168 | 169 | 0 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 0 178 | 0 179 | 180 | 181 | 182 | 183 | 16777215 184 | 16777215 185 | 186 | 187 | 188 | 歌手 189 | 190 | 191 | Qt::PlainText 192 | 193 | 194 | true 195 | 196 | 197 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 198 | 199 | 200 | -2 201 | 202 | 203 | 8 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 0 217 | 0 218 | 219 | 220 | 221 | 222 | 0 223 | 0 224 | 225 | 226 | 227 | 228 | 16777215 229 | 16777215 230 | 231 | 232 | 233 | 专辑 234 | 235 | 236 | true 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 0 245 | 0 246 | 247 | 248 | 249 | 时长 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | MyLabel 258 | QLabel 259 |
mylabel.h
260 |
261 | 262 | EllipsisLabel 263 | QLabel 264 |
ellipsislabel.h
265 |
266 |
267 | 268 | 269 |
270 | -------------------------------------------------------------------------------- /customslider.cpp: -------------------------------------------------------------------------------- 1 | #include "customslider.h" 2 | 3 | CustomSlider::CustomSlider(QWidget *parent) 4 | : QSlider(parent) 5 | { 6 | 7 | } 8 | 9 | // 鼠标点击事件 10 | void CustomSlider::mousePressEvent(QMouseEvent *ev) 11 | { 12 | if (Qt::LeftButton == ev->button()) { 13 | //注意应先调用父类的鼠标点击处理事件,这样可以不影响拖动的情况 14 | QSlider::mousePressEvent(ev); 15 | //获取鼠标的位置,这里并不能直接从ev中取值(因为如果是拖动的话,鼠标开始点击的位置没有意义了) 16 | double pos = ev->x() / (double)width(); 17 | //让进度条直接蹦过来 18 | setValue(pos * (maximum() - minimum()) + minimum()); 19 | //发送自定义的鼠标单击信号 20 | emit customSliderClicked(); 21 | } 22 | 23 | } 24 | 25 | -------------------------------------------------------------------------------- /customslider.h: -------------------------------------------------------------------------------- 1 | #ifndef CUSTOMSLIDER_H 2 | #define CUSTOMSLIDER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class CustomSlider : public QSlider 9 | { 10 | Q_OBJECT 11 | public: 12 | CustomSlider(QWidget *parent = nullptr); 13 | protected: 14 | void mousePressEvent(QMouseEvent *ev);//重写QSlider的mousePressEvent事件 15 | signals: 16 | void customSliderClicked();//自定义的鼠标单击信号,用于捕获并处理 17 | }; 18 | 19 | #endif // CUSTOMSLIDER_H 20 | -------------------------------------------------------------------------------- /ellipsislabel.cpp: -------------------------------------------------------------------------------- 1 | #include "ellipsislabel.h" 2 | 3 | EllipsisLabel::EllipsisLabel(QWidget *parent) 4 | : QLabel{parent} 5 | { 6 | 7 | } 8 | 9 | void EllipsisLabel::setEllipsisText(QString text) 10 | { 11 | this->setToolTip(text); 12 | QString ellipsis = this->fontMetrics().elidedText(text, Qt::ElideRight, this->width()); 13 | this->setText(ellipsis); 14 | } 15 | 16 | void EllipsisLabel::resizeEvent(QResizeEvent *event) 17 | { 18 | QString ellipsis = this->fontMetrics().elidedText(this->toolTip(), Qt::ElideRight, this->width()); 19 | this->setText(ellipsis); 20 | } 21 | -------------------------------------------------------------------------------- /ellipsislabel.h: -------------------------------------------------------------------------------- 1 | #ifndef ELLIPSISLABEL_H 2 | #define ELLIPSISLABEL_H 3 | 4 | #include 5 | 6 | class EllipsisLabel : public QLabel 7 | { 8 | Q_OBJECT 9 | public: 10 | explicit EllipsisLabel(QWidget *parent = nullptr); 11 | void setEllipsisText(QString text); 12 | protected: 13 | void resizeEvent(QResizeEvent *event); 14 | }; 15 | 16 | #endif // ELLIPSISLABEL_H 17 | -------------------------------------------------------------------------------- /gaussianblur.cpp: -------------------------------------------------------------------------------- 1 | #include "gaussianblur.h" 2 | 3 | #include 4 | #include 5 | 6 | GaussianBlur::GaussianBlur(QObject *parent) 7 | : QObject{parent} 8 | { 9 | 10 | } 11 | 12 | void GaussianBlur::gaussBlur(QImage &img, int radius) 13 | { 14 | // 图片缩小 15 | // img.scaled(50, 50); 16 | // 图片高斯模糊处理 17 | gaussBlur2((int*)img.bits(), img.width(), img.height(), radius); 18 | // 发射图片处理完成信号 19 | emit finished(img); 20 | } 21 | 22 | void GaussianBlur::gaussBlur1(int* pix, int w, int h, int radius) 23 | { 24 | float sigma = 1.0 * radius / 2.57; 25 | float deno = 1.0 / (sigma * sqrt(2.0 * M_PI)); 26 | float nume = -1.0 / (2.0 * sigma * sigma); 27 | 28 | float* gaussMatrix = (float*)malloc(sizeof(float)* (radius + radius + 1)); 29 | float gaussSum = 0.0; 30 | for(int i = 0, x = -radius; x <= radius; ++x, ++i) 31 | { 32 | float g = deno * exp(1.0 * nume * x * x); 33 | 34 | gaussMatrix[i] = g; 35 | gaussSum += g; 36 | } 37 | 38 | int len = radius + radius + 1; 39 | for(int i = 0; i < len; ++i) 40 | { 41 | gaussMatrix[i] /= gaussSum; 42 | } 43 | 44 | int* rowData = (int*)malloc(w * sizeof(int)); 45 | int* listData = (int*)malloc(h * sizeof(int)); 46 | 47 | for(int y = 0; y < h; ++y) 48 | { 49 | memcpy(rowData, pix + y * w, sizeof(int) * w); 50 | 51 | for(int x = 0; x < w; ++x) 52 | { 53 | float r = 0, g = 0, b = 0; 54 | gaussSum = 0; 55 | 56 | for(int i = -radius; i <= radius; ++i) 57 | { 58 | int k = x + i; 59 | 60 | if(0 <= k && k <= w) 61 | { 62 | int color = rowData[k]; 63 | int cr = (color & 0x00ff0000) >> 16; 64 | int cg = (color & 0x0000ff00) >> 8; 65 | int cb = (color & 0x000000ff); 66 | 67 | r += cr * gaussMatrix[i + radius]; 68 | g += cg * gaussMatrix[i + radius]; 69 | b += cb * gaussMatrix[i + radius]; 70 | 71 | gaussSum += gaussMatrix[i + radius]; 72 | } 73 | } 74 | 75 | int cr = (int)(r / gaussSum); 76 | int cg = (int)(g / gaussSum); 77 | int cb = (int)(b / gaussSum); 78 | 79 | pix[y * w + x] = cr << 16 | cg << 8 | cb | 0xff000000; 80 | } 81 | } 82 | 83 | for(int x = 0; x < w; ++x) 84 | { 85 | for(int y = 0; y < h; ++y) 86 | { 87 | listData[y] = pix[y * w + x]; 88 | } 89 | 90 | for(int y = 0; y < h; ++y) 91 | { 92 | float r = 0, g = 0, b = 0; 93 | gaussSum = 0; 94 | 95 | for(int j = -radius; j <= radius; ++j) 96 | { 97 | int k = y + j; 98 | 99 | if(0 <= k && k <= h) 100 | { 101 | int color = listData[k]; 102 | int cr = (color & 0x00ff0000) >> 16; 103 | int cg = (color & 0x0000ff00) >> 8; 104 | int cb = (color & 0x000000ff); 105 | 106 | r += cr * gaussMatrix[j + radius]; 107 | g += cg * gaussMatrix[j + radius]; 108 | b += cb * gaussMatrix[j + radius]; 109 | 110 | gaussSum += gaussMatrix[j + radius]; 111 | } 112 | } 113 | 114 | int cr = (int)(r / gaussSum); 115 | int cg = (int)(g / gaussSum); 116 | int cb = (int)(b / gaussSum); 117 | 118 | pix[y * w + x] = cr << 16 | cg << 8 | cb | 0xff000000; 119 | } 120 | } 121 | 122 | free(gaussMatrix); 123 | free(rowData); 124 | free(listData); 125 | } 126 | 127 | void GaussianBlur::gaussBlur2(int* pix, int w, int h, int radius) 128 | { 129 | float sigma = 1.0 * radius / 2.57; 130 | 131 | int boxSize = 3; 132 | int* boxR = (int*)malloc(sizeof(int) * boxSize); 133 | 134 | boxesForGauss(sigma, boxR, boxSize); 135 | 136 | int* tempPix = (int*)malloc(sizeof(int) * w * h); 137 | 138 | boxBlur(pix, tempPix, w, h, (boxR[0] - 1) / 2); 139 | boxBlur(pix, tempPix, w, h, (boxR[1] - 1) / 2); 140 | boxBlur(pix, tempPix, w, h, (boxR[2] - 1) / 2); 141 | 142 | free(boxR); 143 | free(tempPix); 144 | } 145 | 146 | void GaussianBlur::boxBlurH(int* srcPix, int* destPix, int w, int h, int radius) 147 | { 148 | int index, r = 0, g = 0, b = 0; 149 | int tr, tg, tb; 150 | 151 | int color, preColor; 152 | 153 | int num; 154 | float iarr; 155 | 156 | for(int i = 0; i < h; ++i) 157 | { 158 | r = 0; 159 | g = 0; 160 | b = 0; 161 | 162 | index = i * w; 163 | num = radius; 164 | 165 | for(int j = 0; j < radius; ++j) 166 | { 167 | color = srcPix[index + j]; 168 | r += (color & 0x00ff0000) >> 16; 169 | g += (color & 0x0000ff00) >> 8; 170 | b += (color & 0x000000ff); 171 | } 172 | 173 | for(int j = 0; j <= radius; ++j) 174 | { 175 | num++; 176 | iarr = 1.0 / (1.0 * num); 177 | 178 | color = srcPix[index + j + radius]; 179 | r += (color & 0x00ff0000) >> 16; 180 | g += (color & 0x0000ff00) >> 8; 181 | b += (color & 0x000000ff); 182 | 183 | tr = (int)(r * iarr); 184 | tg = (int)(g * iarr); 185 | tb = (int)(b * iarr); 186 | 187 | destPix[index + j] = tr << 16 | tg << 8 | tb | 0xff000000; 188 | } 189 | 190 | iarr = 1.0 / (1.0 * num); 191 | for(int j = radius + 1; j < w - radius; ++j) 192 | { 193 | preColor = srcPix[index + j - 1 - radius]; 194 | color = srcPix[index + j + radius]; 195 | 196 | r = r + ((color & 0x00ff0000) >> 16) - ((preColor & 0x00ff0000) >> 16); 197 | g = g + ((color & 0x0000ff00) >> 8) - ((preColor & 0x0000ff00) >> 8); 198 | b = b + (color & 0x000000ff) - (preColor & 0x000000ff); 199 | 200 | tr = (int)(r * iarr); 201 | tg = (int)(g * iarr); 202 | tb = (int)(b * iarr); 203 | 204 | destPix[index + j] = tr << 16 | tg << 8 | tb | 0xff000000; 205 | } 206 | 207 | for(int j = w - radius; j < w; ++j) 208 | { 209 | num--; 210 | iarr = 1.0 / (1.0 * num); 211 | 212 | preColor = srcPix[index + j - 1 - radius]; 213 | 214 | r -= (preColor & 0x00ff0000) >> 16; 215 | g -= (preColor & 0x0000ff00) >> 8; 216 | b -= (preColor & 0x000000ff); 217 | 218 | tr = (int)(r * iarr); 219 | tg = (int)(g * iarr); 220 | tb = (int)(b * iarr); 221 | 222 | destPix[index + j] = tr << 16 | tg << 8 | tb | 0xff000000; 223 | } 224 | } 225 | } 226 | 227 | void GaussianBlur::boxBlurV(int* srcPix, int* destPix, int w, int h, int radius) 228 | { 229 | int r = 0, g = 0, b = 0; 230 | int tr, tg, tb; 231 | 232 | int color, preColor; 233 | 234 | int num; 235 | float iarr; 236 | 237 | for(int i = 0; i < w; ++i) 238 | { 239 | r = 0; 240 | g = 0; 241 | b = 0; 242 | 243 | num = radius; 244 | 245 | for(int j = 0; j < radius; ++j) 246 | { 247 | color = srcPix[j*w + i]; 248 | r += (color & 0x00ff0000) >> 16; 249 | g += (color & 0x0000ff00) >> 8; 250 | b += (color & 0x000000ff); 251 | } 252 | 253 | for(int j = 0; j <= radius; ++j) 254 | { 255 | num++; 256 | iarr = 1.0 / (1.0 * num); 257 | 258 | color = srcPix[(j + radius) * w + i]; 259 | r += (color & 0x00ff0000) >> 16; 260 | g += (color & 0x0000ff00) >> 8; 261 | b += (color & 0x000000ff); 262 | 263 | tr = (int)(r * iarr); 264 | tg = (int)(g * iarr); 265 | tb = (int)(b * iarr); 266 | 267 | destPix[j*w + i] = tr << 16 | tg << 8 | tb | 0xff000000; 268 | } 269 | 270 | iarr = 1.0 / (1.0 * num); 271 | for(int j = radius + 1; j < h - radius; ++j) 272 | { 273 | preColor = srcPix[(j - radius - 1) * w + i]; 274 | color = srcPix[(j + radius) * w + i]; 275 | 276 | r = r + ((color & 0x00ff0000) >> 16) - ((preColor & 0x00ff0000) >> 16); 277 | g = g + ((color & 0x0000ff00) >> 8) - ((preColor & 0x0000ff00) >> 8); 278 | b = b + (color & 0x000000ff) - (preColor & 0x000000ff); 279 | 280 | tr = (int)(r * iarr); 281 | tg = (int)(g * iarr); 282 | tb = (int)(b * iarr); 283 | 284 | destPix[j*w + i] = tr << 16 | tg << 8 | tb | 0xff000000; 285 | } 286 | 287 | for(int j = h - radius; j < h; ++j) 288 | { 289 | num--; 290 | iarr = 1.0 / (1.0 * num); 291 | preColor = srcPix[(j - radius - 1) * w + i]; 292 | 293 | r -= (preColor & 0x00ff0000) >> 16; 294 | g -= (preColor & 0x0000ff00) >> 8; 295 | b -= (preColor & 0x000000ff); 296 | 297 | tr = (int)(r * iarr); 298 | tg = (int)(g * iarr); 299 | tb = (int)(b * iarr); 300 | 301 | destPix[j*w + i] = tr << 16 | tg << 8 | tb | 0xff000000; 302 | } 303 | } 304 | } 305 | 306 | void GaussianBlur::boxBlur(int* srcPix, int* destPix, int w, int h, int r) 307 | { 308 | if(r < 0) 309 | { 310 | return; 311 | } 312 | 313 | boxBlurH(srcPix, destPix, w, h, r); 314 | boxBlurV(destPix, srcPix, w, h, r); 315 | } 316 | 317 | void GaussianBlur::boxesForGauss(float sigma, int* size, int n) 318 | { 319 | float wIdeal = sqrt(12.0 * sigma * sigma / n + 1.0); 320 | int wl = floor(wIdeal); 321 | 322 | if(0 == wl % 2) 323 | { 324 | wl--; 325 | } 326 | 327 | int wu = wl + 2; 328 | 329 | float mIdeal = (12.0 * sigma * sigma - n * wl * wl - 4 * n * wl - 3 * n) / (-4 * wl - 4); 330 | int m = round(mIdeal); 331 | 332 | for(int i = 0; i < n; ++i) 333 | { 334 | size[i] = (i < m ? wl : wu); 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /gaussianblur.h: -------------------------------------------------------------------------------- 1 | #ifndef GAUSSIANBLUR_H 2 | #define GAUSSIANBLUR_H 3 | 4 | #include 5 | #include 6 | 7 | /* 8 | * QImage图片高斯模糊处理 9 | */ 10 | class GaussianBlur : public QObject 11 | { 12 | Q_OBJECT 13 | public: 14 | explicit GaussianBlur(QObject *parent = nullptr); 15 | // 图片高斯模糊 16 | void gaussBlur(QImage &img, int radius); 17 | signals: 18 | // 图片处理完成信号 19 | void finished(QImage image); 20 | private: 21 | void gaussBlur1(int* pix, int w, int h, int radius); 22 | void gaussBlur2(int* pix, int w, int h, int radius); 23 | void boxBlurH(int* srcPix, int* destPix, int w, int h, int radius); 24 | void boxBlurV(int* srcPix, int* destPix, int w, int h, int radius); 25 | void boxBlur(int* srcPix, int* destPix, int w, int h, int r); 26 | void boxesForGauss(float sigma, int* size, int n); 27 | }; 28 | 29 | #endif // GAUSSIANBLUR_H 30 | -------------------------------------------------------------------------------- /haomusic.h: -------------------------------------------------------------------------------- 1 | #ifndef HAOMUSIC_H 2 | #define HAOMUSIC_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "addsonglistpage.h" 15 | #include "customitem.h" 16 | #include "music.h" 17 | #include "musicdb.h" 18 | #include "musiclist.h" 19 | #include "myhttp.h" 20 | #include "mymediaplaylist.h" 21 | #include "switchanimation.h" 22 | #include "searchtipslist.h" 23 | 24 | QT_BEGIN_NAMESPACE 25 | namespace Ui { class HaoMusic; } 26 | QT_END_NAMESPACE 27 | 28 | /* 29 | * 主界面 30 | */ 31 | class HaoMusic : public SwitchAnimation 32 | { 33 | Q_OBJECT 34 | 35 | public: 36 | HaoMusic(QWidget *parent = nullptr); 37 | ~HaoMusic(); 38 | protected: 39 | // 鼠标按下事件 40 | void mousePressEvent(QMouseEvent *event); 41 | // 鼠标移动事件 42 | void mouseMoveEvent(QMouseEvent *event); 43 | // 鼠标释放事件 44 | void mouseReleaseEvent(QMouseEvent *event); 45 | // 鼠标双击事件 46 | void mouseDoubleClickEvent(QMouseEvent *event); 47 | // 绘图事件 48 | void paintEvent(QPaintEvent *event); 49 | private: 50 | Ui::HaoMusic *ui; 51 | AddSongListPage *addSongListPage = nullptr; 52 | // QMap songLists; 53 | SearchTipsList *searchTips = nullptr; // 搜索提示框 54 | 55 | MyMediaPlaylist *mediaPlaylist = nullptr; // 媒体播放列表 56 | QMediaPlayer *mediaPlayer = nullptr; // 媒体播放器 57 | MyHttp *search = nullptr; // 网络搜索 58 | CustomItem *currentPlayingItem = nullptr; // 当前正在播放的item 59 | 60 | QPoint m_mousePoint; // 鼠标坐标 61 | QPoint movePoint; // 窗口移动距离 62 | bool mousePress = false; // 鼠标左键是否按下 63 | 64 | int listType = -1; // 列表类型 65 | 66 | int volume = 30; // 初始音量 67 | bool isSearchResultUpdate = false; // 搜索结果是否更新 68 | bool isFavoriteMusicListShow = false; // 我喜欢的音乐列表是否显示 69 | bool isLocalMusicListShow = false; // 本地音乐列表是否显示 70 | bool isSonglistShow = false; // 我的歌单是否显示 71 | bool isHistoryMusicListShow = false; // 历史音乐列表是否显示 72 | 73 | int pressInterval = 500; // 按钮防抖时间 74 | int loadingTimes = 1000; // 加载所需时间(ms) 75 | QString searchKeywords = ""; // 搜索关键词 76 | QMovie *loadingMovie; // 加载动画 77 | 78 | Music currentMusic; // 当前播放的音乐 79 | int currentLrcRow = 1; // 当前歌词所在行 80 | QListWidgetItem *currentLrcItem = nullptr; // 当前歌词所在item 81 | QMap lrcMap; // 歌词 82 | QList lrcKeys; // 歌词对应的时间帧 83 | 84 | QList musicList; // 播放列表音乐列表 85 | QList searchResultMusicList; // 搜索结果音乐列表 86 | QList favoriteMusicList; // 我喜欢的音乐列表 87 | QList localMusicList; // 本地音乐列表 88 | QList historyMusicList; // 播放历史列表 89 | 90 | MusicDB *m_db; // 音乐数据库 91 | // 读取配置 92 | void readSettings(); 93 | // 写入配置 94 | void writeSettings(); 95 | // 绘制圆角阴影窗口 96 | void paintShadowRadiusWidget(); 97 | // 设置托盘图标 98 | void setTrayIcon(); 99 | // 初始化播放器 100 | void initPlayer(); 101 | // 初始化音乐列表 102 | void initMusicList(); 103 | // 连接信号和槽 104 | void connectSignalsAndSlots(); 105 | // 音乐进度条点击事件处理函数 106 | void sliderClicked(); 107 | // 显示搜索结果 108 | void showSearchResult(); 109 | // 更新播放列表 110 | void updateMediaPlaylist(); 111 | // 更新底部栏音乐信息 112 | void updateBottomMusicInfo(); 113 | // 显示歌词 114 | void showLrc(); 115 | // 歌词滚动 116 | void lrcRoll(int position); 117 | // 显示加载界面 118 | void showLoadingPage(); 119 | // 更改当前播放项的样式 120 | void changeCurrentPlayingItem(CustomItem *item); 121 | // 播放当前项 122 | void playingTheItem(CustomItem *item); 123 | // 显示歌词页面 124 | void showLrcPage(); 125 | // 双击列表项 126 | void onCustomItemDoubleClicked(CustomItem *item); 127 | // 添加到历史播放列表 128 | void addToHistoryList(Music &music); 129 | // 打开我创建的音乐列表 130 | void openMyMusicList(int id, QString name); 131 | // 打开搜索提示框 132 | void showSearchTips(bool show = true); 133 | private slots: 134 | // 双击托盘图标显示界面 135 | void iconActived(QSystemTrayIcon::ActivationReason); 136 | // 关闭窗口 137 | void on_pushButton_close_clicked(); 138 | // 窗口最大化 139 | void on_pushButton_maxsize_clicked(); 140 | // 窗口最小化 141 | void on_pushButton_minsize_clicked(); 142 | // 点击搜索按钮 143 | void on_pushButton_search_clicked(); 144 | // 点击上一首 145 | void on_pushButton_lastsong_clicked(); 146 | // 点击播放按钮 147 | void on_pushButton_switch_clicked(); 148 | // 点击下一首 149 | void on_pushButton_nextsong_clicked(); 150 | // 点击播放模式切换按钮 151 | void on_pushButton_mode_clicked(); 152 | // 点击静音按钮 153 | void on_pushButton_volume_clicked(); 154 | // 正在播放音乐的当前时刻变化处理函数 155 | void onPositionChanged(qint64); 156 | // 音量条变化处理函数 157 | void on_horizontalSlider_volume_valueChanged(int value); 158 | // 点击历史播放按钮 159 | void on_pushButton_recentlyplayed_clicked(); 160 | // 点击默认歌单按钮 161 | void on_pushButton_defaultSongList_clicked(); 162 | // 点击本地音乐按钮 163 | void on_pushButton_localmusic_clicked(); 164 | // 点击歌词界面下拉按钮 165 | void on_pushButton_dropDown_clicked(); 166 | // 点击我喜欢的音乐按钮 167 | void on_pushButton_favorite_clicked(); 168 | // 右键菜单点击事件 169 | void menuPlayMusicClicked(CustomItem *item); 170 | void menuAddToMyFavoriteClicked(Music music); 171 | void menuAddToSonglistClicked(Music music); 172 | void menuRemoveFromSongList(CustomItem *item); 173 | // 点击列表的某一行 174 | void on_listWidget_lrc_itemClicked(QListWidgetItem *item); 175 | // 搜索框内容变化处理函数 176 | void on_lineEdit_search_textChanged(const QString &arg1); 177 | // 菜单点击事件处理函数 178 | void onMenuClicked(CustomItem *item, int type); 179 | // 向歌单添加歌曲 180 | void addMusicToSonglist(int id, Music music); 181 | // 创建歌单 182 | void createSonglist(int id, QString name); 183 | // 歌曲列表加载完成 184 | void listLoaded(int id); 185 | void on_listWidget_songList_itemClicked(QListWidgetItem *item); 186 | void on_lineEdit_listName_returnPressed(); 187 | void on_pushButton_newSongList_clicked(); 188 | }; 189 | 190 | #endif // HAOMUSIC_H 191 | -------------------------------------------------------------------------------- /icon/Max2normal.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/Music.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/MusicPlayer.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhyxy/MusicPlayer-in-Qt-Plus/75899ecc9480d4b3b207fcddfa1c0abced6411eb/icon/MusicPlayer.ico -------------------------------------------------------------------------------- /icon/Player, pause.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/Player, play.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/add.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/downArow_black.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/downArow_gray.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/folder-open.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhyxy/MusicPlayer-in-Qt-Plus/75899ecc9480d4b3b207fcddfa1c0abced6411eb/icon/loading.gif -------------------------------------------------------------------------------- /icon/max.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/min.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/music_last.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/music_next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/noVolume.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/order-play-fill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/pkq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhyxy/MusicPlayer-in-Qt-Plus/75899ecc9480d4b3b207fcddfa1c0abced6411eb/icon/pkq.jpg -------------------------------------------------------------------------------- /icon/player-list-add.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/player-pause.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/player-skip-back.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/player-skip-forward.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/player_paly.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/queue_music.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/random.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/repeat-one-line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/repeat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/rounded .svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/volume.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /iconlist.cpp: -------------------------------------------------------------------------------- 1 | #include "iconlist.h" 2 | #include "musicdb.h" 3 | #include "coveritem.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | IconList::IconList(QWidget *parent) 13 | : QListWidget{parent} 14 | { 15 | iconSize = 210; 16 | wordHight = 30; 17 | margin = 20; 18 | this->setViewMode(QListWidget::IconMode); // 显示模式 19 | this->setSpacing(30); // 间距 20 | this->setIconSize(QSize(iconSize + 2 *margin, iconSize + 2 * margin)); // 设置图片大小 21 | this->setMovement(QListView::Static); // 不能移动 22 | this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 23 | //listWidget属性设置为自定义菜单 24 | this->setContextMenuPolicy(Qt::CustomContextMenu); 25 | connect(this, &IconList::customContextMenuRequested, this, &IconList::onCustomContextMenuRequested); 26 | } 27 | 28 | void IconList::init() 29 | { 30 | this->clear(); 31 | addIcons(MusicDB::instance()->queryList()); 32 | } 33 | 34 | void IconList::addIcons(QList lists) 35 | { 36 | foreach (MusicList list, lists) { 37 | addIcon(list); 38 | } 39 | } 40 | 41 | void IconList::addIcon(MusicList& list) 42 | { 43 | QListWidgetItem *imageItem = new QListWidgetItem(); 44 | imageItem->setSizeHint(QSize(iconSize + 2 *margin, iconSize + 2* margin + wordHight)); 45 | imageItem->setTextAlignment(Qt::AlignHCenter | Qt::AlignBottom); 46 | imageItem->setData(Qt::UserRole, list.id()); 47 | imageItem->setData(Qt::UserRole + 1, list.name()); 48 | 49 | 50 | CoverItem* item = new CoverItem(); 51 | item->setMargin(margin); 52 | item->resize(imageItem->sizeHint()); 53 | item->setCoverSize(QSize(iconSize, iconSize)); 54 | item->setCover(list.cover()); 55 | item->setName(list.name()); 56 | 57 | this->addItem(imageItem); 58 | this->setItemWidget(imageItem, item); 59 | } 60 | 61 | void IconList::resizeEvent(QResizeEvent *e) 62 | { 63 | int width = this->width(); // listwidget的宽度 64 | int num; 65 | if (width < 1500) 66 | num = 4; 67 | else 68 | num = 5; 69 | int spacing = (width - (num * (iconSize + 2 *margin))) / num - num * num; 70 | this->setSpacing(spacing); 71 | QListWidget::resizeEvent(e); 72 | } 73 | 74 | void IconList::dropSongsList(QListWidgetItem *item) 75 | { 76 | int id = item->data(Qt::UserRole).toInt(); 77 | if (id <= MyListWidget::DEFALUT) 78 | return; 79 | delete this->takeItem(this->row(item)); 80 | MusicDB::instance()->removeList(id); 81 | init(); 82 | } 83 | 84 | void IconList::renameSongsList(QListWidgetItem *item) 85 | { 86 | 87 | } 88 | 89 | void IconList::creatSongsList() 90 | { 91 | emit create(); 92 | } 93 | 94 | void IconList::onCustomContextMenuRequested(const QPoint &pos) 95 | { 96 | Q_UNUSED(pos) 97 | 98 | QMenu* menu = new QMenu(this); 99 | // 菜单属性设置为无边框无阴影透明背景 100 | menu->setWindowFlags(menu->windowFlags() | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint); 101 | menu->setAttribute(Qt::WA_TranslucentBackground); 102 | menu->setAttribute(Qt::WA_DeleteOnClose); 103 | // 给菜单设置阴影 104 | QGraphicsDropShadowEffect *effect = new QGraphicsDropShadowEffect(this); 105 | effect->setOffset(0,0); 106 | effect->setColor(QColor(150,150,150)); 107 | effect->setBlurRadius(10); 108 | menu->setGraphicsEffect(effect); 109 | 110 | QAction* create = new QAction("新建歌单", menu); 111 | connect(create, &QAction::triggered, this, [=] { 112 | creatSongsList(); 113 | }); 114 | menu->addAction(create); 115 | 116 | QListWidgetItem *item = this->itemAt(mapFromGlobal(QCursor::pos())); 117 | if (item != nullptr) { 118 | int id = item->data(Qt::UserRole).toInt(); 119 | QAction * rename = new QAction("重命名", menu); 120 | QAction * drop = new QAction("删除歌单", menu); 121 | menu->addAction(rename); 122 | menu->addAction(drop); 123 | connect(rename, &QAction::triggered, [=] { 124 | renameSongsList(item); 125 | }); 126 | if (id > MyListWidget::DEFALUT) 127 | connect(drop, &QAction::triggered, [=] { 128 | dropSongsList(item); 129 | }); 130 | } 131 | menu->exec(QCursor::pos()); 132 | } 133 | -------------------------------------------------------------------------------- /iconlist.h: -------------------------------------------------------------------------------- 1 | #ifndef ICONLIST_H 2 | #define ICONLIST_H 3 | 4 | #include "coveritem.h" 5 | #include 6 | #include 7 | 8 | class IconList : public QListWidget 9 | { 10 | Q_OBJECT 11 | public: 12 | explicit IconList(QWidget *parent = nullptr); 13 | void init(); 14 | void addIcons(QList lists); 15 | void addIcon(MusicList& list); 16 | signals: 17 | void create(); 18 | protected: 19 | 20 | void resizeEvent(QResizeEvent *e); 21 | private: 22 | int iconSize; 23 | int wordHight; 24 | int margin; 25 | private slots: 26 | void dropSongsList(QListWidgetItem *item); 27 | void renameSongsList(QListWidgetItem *item); 28 | void creatSongsList(); 29 | void onCustomContextMenuRequested(const QPoint &pos); 30 | }; 31 | 32 | #endif // ICONLIST_H 33 | -------------------------------------------------------------------------------- /lrcwidget.cpp: -------------------------------------------------------------------------------- 1 | #include "lrcwidget.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | LrcWidget::LrcWidget(QWidget *parent) 14 | : QWidget{parent} 15 | { 16 | // 初始化 17 | manager = new QNetworkAccessManager(this); 18 | gauss = new GaussianBlur(this); 19 | // this->adjustSize(); 20 | radius = 300; 21 | // label = new MyLabel(this); 22 | 23 | // label->setBackRole(); 24 | // label->lower(); 25 | // this->update(); 26 | // 网络请求完成 27 | connect(manager, &QNetworkAccessManager::finished, this, &LrcWidget::onRequestFinished); 28 | // 图片模糊处理完成 29 | connect(gauss, &GaussianBlur::finished, this, &LrcWidget::onGaussianFinished); 30 | } 31 | 32 | //// 绘图事件 33 | void LrcWidget::paintEvent(QPaintEvent *event) 34 | { 35 | // 重绘背景 36 | repaintBackground(); 37 | QWidget::paintEvent(event); 38 | } 39 | 40 | void LrcWidget::resizeEvent(QResizeEvent *event) 41 | { 42 | updated = true; 43 | // 重绘背景 44 | repaintBackground(); 45 | } 46 | 47 | // 设置背景为高斯模糊后的专辑图片 48 | void LrcWidget::setGaussblurBackground(const QString &url) 49 | { 50 | update(); 51 | QNetworkRequest request; 52 | QString minUrl = url + "?param=1000y1000"; 53 | request.setUrl(QUrl(minUrl)); 54 | manager->get(request); 55 | } 56 | 57 | // 重新绘制背景图 58 | void LrcWidget::repaintBackground() 59 | { 60 | // 背景图不为空 61 | if (img.isNull()) 62 | return; 63 | // 仅在背景图片更新时重绘背景 64 | if (!updated) { 65 | return; 66 | } 67 | updated = false; 68 | // 绘制背景图片 69 | std::lock_guard lck(mutex); 70 | this->setAutoFillBackground(true); 71 | QPalette pal = this->palette(); 72 | pal.setBrush(QPalette::Window, QBrush(img.scaled(this->size()))); 73 | this->setPalette(pal); 74 | } 75 | 76 | // 网络应答处理 77 | void LrcWidget::onRequestFinished(QNetworkReply *reply) 78 | { 79 | // 获取响应状态码,200表示正常 80 | int status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 81 | qDebug()<<__FILE__<<__LINE__ << "状态码:" << status_code; 82 | 83 | if (reply->error() == QNetworkReply::NoError) { 84 | // 获取字节数据 85 | QByteArray bytes = reply->readAll(); 86 | // 转换为图片 87 | QImage img; 88 | img.loadFromData(bytes); 89 | // 在线程中模糊背景图片 90 | QtConcurrent::run(gauss, GaussianBlur::gaussBlur, img, radius); 91 | }else { 92 | qDebug() << __FILE__ << __LINE__ << "获取专辑图片失败"; 93 | } 94 | reply->deleteLater(); 95 | } 96 | 97 | // 高斯模糊图片显示 98 | void LrcWidget::onGaussianFinished(QImage image) 99 | { 100 | img = image; 101 | updated = true; 102 | update(); 103 | } 104 | 105 | QLabel *LrcWidget::getLabel() 106 | { 107 | return label; 108 | } 109 | 110 | 111 | -------------------------------------------------------------------------------- /lrcwidget.h: -------------------------------------------------------------------------------- 1 | #ifndef LRCWIDGET_H 2 | #define LRCWIDGET_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "gaussianblur.h" 9 | #include "mylabel.h" 10 | /* 11 | * 歌词界面 12 | */ 13 | class LrcWidget : public QWidget 14 | { 15 | Q_OBJECT 16 | public: 17 | explicit LrcWidget(QWidget *parent = nullptr); 18 | // 设置高斯模糊背景图片 19 | void setGaussblurBackground(const QString &url); 20 | QLabel *getLabel(); 21 | protected: 22 | // 绘图事件 23 | void paintEvent(QPaintEvent *event); 24 | void resizeEvent(QResizeEvent *event); 25 | private slots: 26 | // 请求完成 27 | void onRequestFinished(QNetworkReply *reply); 28 | // 图片模糊处理完成 29 | void onGaussianFinished(QImage image); 30 | 31 | private: 32 | QNetworkAccessManager *manager; // 网络请求管理 33 | GaussianBlur *gauss; // 图片高斯模糊类 34 | int radius; // 模糊半径 35 | QImage img; // 背景图片 36 | std::mutex mutex; 37 | bool updated = false; 38 | MyLabel *label; 39 | private: 40 | // 重绘背景图 41 | void repaintBackground(); 42 | }; 43 | 44 | #endif // LRCWIDGET_H 45 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "haomusic.h" 2 | 3 | #include 4 | #include 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | QApplication a(argc, argv); 9 | // 连接数据库 10 | MusicDB::instance()->open(); 11 | HaoMusic w; 12 | w.setGeometry(253, 32, 1503, 970); 13 | //去掉标题栏 点击任务栏图标显示/隐藏窗口 14 | w.setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint); 15 | // 外层窗口透明 16 | w.setAttribute(Qt::WA_TranslucentBackground); 17 | w.setAutoFillBackground(false); 18 | QThreadPool::globalInstance()->setMaxThreadCount(10); // 设置最大线程个数为10 19 | w.show(); 20 | return a.exec(); 21 | } 22 | -------------------------------------------------------------------------------- /music.cpp: -------------------------------------------------------------------------------- 1 | #include "music.h" 2 | #include 3 | 4 | Music::Music() 5 | { 6 | 7 | } 8 | 9 | Music::Music(int id, QString songName, QString author, QString albumName, QString albumPicUrl, int songDuration) 10 | :m_id(id), 11 | m_songName(songName), 12 | m_author(author), 13 | m_albumName(albumName), 14 | m_albumPicUrl(albumPicUrl), 15 | m_songDuration(songDuration) 16 | { 17 | } 18 | 19 | bool Music::operator ==(const Music &music) 20 | { 21 | return this->getId() == music.getId(); 22 | } 23 | 24 | bool Music::empty() 25 | { 26 | if (m_id) 27 | return false; 28 | return true; 29 | } 30 | 31 | QString Music::albumPicUrl() const 32 | { 33 | return m_albumPicUrl; 34 | } 35 | 36 | QString Music::getSongDuration() const 37 | { 38 | return QTime(0, 0, 0, 0).addMSecs(m_songDuration).toString("mm:ss"); 39 | } 40 | 41 | int Music::songDuration() const 42 | { 43 | return m_songDuration; 44 | } 45 | 46 | QString Music::getAuthor() const 47 | { 48 | if (m_author.isEmpty()) 49 | return "未知"; 50 | return m_author; 51 | } 52 | 53 | QString Music::getAlbumName() const 54 | { 55 | if (m_albumName.isEmpty()) 56 | return "未知"; 57 | return m_albumName; 58 | } 59 | 60 | QString Music::getSongName() const 61 | { 62 | if (m_songName.isEmpty()) 63 | return "未知"; 64 | return m_songName; 65 | } 66 | 67 | 68 | int Music::getId() const 69 | { 70 | return m_id; 71 | } 72 | 73 | Music::Music(int id, const QString &songName, const QString &author, const QString &albumPicUrl) : m_id(id), 74 | m_songName(songName), 75 | m_author(author), 76 | m_albumPicUrl(albumPicUrl) 77 | {} 78 | -------------------------------------------------------------------------------- /music.h: -------------------------------------------------------------------------------- 1 | #ifndef MUSIC_H 2 | #define MUSIC_H 3 | 4 | #include 5 | 6 | /* 7 | * 音乐信息存储类 8 | */ 9 | class Music 10 | { 11 | public: 12 | explicit Music(); 13 | Music(int id, const QString &songName, const QString &author, const QString &albumPicUrl); 14 | explicit Music(int id, QString songName, QString author, QString albumName, QString albumPicUrl, int songDuration); 15 | // 重写==操作符 16 | bool operator ==(const Music &music); 17 | // 判空 18 | bool empty(); 19 | // geter 20 | int getId() const; 21 | QString getSongName() const; 22 | QString getAuthor() const; 23 | QString getAlbumName() const; 24 | QString albumPicUrl() const; 25 | QString getSongDuration() const; 26 | int songDuration() const; 27 | 28 | private: 29 | int m_id = 0; // 歌曲Id 30 | QString m_songName; // 歌曲名称 31 | QString m_author; // 歌手名称 32 | QString m_albumName; // 专辑名称 33 | QString m_albumPicUrl; // 专辑图片链接 34 | QString m_songUrl; // 歌曲播放链接 35 | int m_songDuration; // 歌曲时长 36 | }; 37 | 38 | // 注册Qariant 39 | Q_DECLARE_METATYPE(Music) 40 | 41 | #endif // MUSIC_H 42 | -------------------------------------------------------------------------------- /musicdb.cpp: -------------------------------------------------------------------------------- 1 | #include "musicdb.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | Q_GLOBAL_STATIC(MusicDB, db) 10 | 11 | MusicDB::MusicDB() 12 | { 13 | } 14 | 15 | MusicDB *MusicDB::instance() 16 | { 17 | return db(); 18 | } 19 | 20 | // 连接数据库 21 | void MusicDB::open() 22 | { 23 | //"qt_sql_default_connection" 24 | if (QSqlDatabase::contains("qt_sql_default_connection")) { 25 | m_db = QSqlDatabase::database("qt_sql_default_connection"); 26 | } else { 27 | m_db = QSqlDatabase::addDatabase("QSQLITE"); 28 | m_db.setDatabaseName("Music.db"); 29 | } 30 | if (!m_db.open()) { 31 | qDebug() << __FILE__ << __LINE__ << "Error: Failed to connect database." << m_db.lastError(); 32 | } 33 | QSqlQuery query(m_db); 34 | qDebug() << __FILE__ << __LINE__ << "Succeed to connect database." ; 35 | 36 | QString create_sql = "CREATE TABLE IF NOT EXISTS song (" 37 | "l_id integer NOT NULL," 38 | "id integer NOT NULL," 39 | "name TEXT NOT NULL," 40 | "author TEXT NOT NULL," 41 | "album TEXT NOT NULL," 42 | "picurl TEXT NOT NULL," 43 | "duration integer NOT NULL" 44 | ");"; 45 | query.prepare(create_sql); 46 | if(!query.exec()) { 47 | qDebug() << __FILE__ << __LINE__ << "Error: Fail to create table." << query.lastError(); 48 | } 49 | query.clear(); 50 | 51 | QString isTableExist = "select count(*) from sqlite_master where type = 'table' and name = 'musiclist';"; 52 | if(!query.exec(isTableExist)) { 53 | qDebug() << __FILE__ << __LINE__ << "Error: Fail to query." << query.lastError(); 54 | } 55 | query.next(); 56 | // 表不存在,创建list表 57 | if(query.value(0).toInt() == 0) { 58 | create_sql = "CREATE TABLE musiclist ( " 59 | "l_id integer NOT NULL PRIMARY KEY AUTOINCREMENT, " 60 | "l_name TEXT NOT NULL, " 61 | "l_cover TEXT, " 62 | "l_intro TEXT" 63 | ");"; 64 | query.prepare(create_sql); 65 | if(!query.exec()) { 66 | qDebug() << __FILE__ << __LINE__ << "Error: Fail to create." << query.lastError(); 67 | } 68 | query.finish(); 69 | MusicList list("本地音乐"); 70 | insertList(list); 71 | 72 | list.setName("历史音乐"); 73 | insertList(list); 74 | 75 | list.setName("我喜欢的音乐"); 76 | insertList(list); 77 | 78 | list.setName("默认歌单"); 79 | insertList(list); 80 | } else { 81 | qDebug() << __FILE__ << __LINE__ << "table musiclist existed"; 82 | } 83 | } 84 | 85 | // 插入音乐 86 | void MusicDB::insert(Music &music, int l_id) 87 | { 88 | QSqlQuery song_query(m_db); 89 | QString insert_sql = "INSERT INTO song (l_id, id, name, author, album, picurl, duration) " 90 | "VALUES (:l_id, :id, :name, :author, :album, :picurl, :duration);"; 91 | song_query.prepare(insert_sql); 92 | song_query.bindValue(":l_id", QString::number(l_id)); 93 | song_query.bindValue(":id", QString::number(music.getId())); 94 | song_query.bindValue(":name", music.getSongName()); 95 | song_query.bindValue(":author", music.getAuthor()); 96 | song_query.bindValue(":album", music.getAlbumName()); 97 | song_query.bindValue(":picurl", music.albumPicUrl()); 98 | song_query.bindValue(":duration", QString::number(music.songDuration())); 99 | if(!song_query.exec()) { 100 | qDebug() << __FILE__ << __LINE__ << "insert error: "< MusicDB::query(int l_id) 109 | { 110 | QSqlQuery song_query(m_db); 111 | QString querySql = "SELECT id, name, author, album, picurl, duration FROM song WHERE l_id = :l_id;"; 112 | song_query.prepare(querySql); 113 | song_query.bindValue(":l_id", QString::number(l_id)); 114 | if (!song_query.exec()) { 115 | qDebug() << __FILE__ << __LINE__ << "query error: "< musicList; 118 | while (song_query.next()) { 119 | int id = song_query.value(0).toInt(); 120 | QString name = song_query.value(1).toString(); 121 | QString author = song_query.value(2).toString(); 122 | QString album = song_query.value(3).toString(); 123 | QString picurl = song_query.value(4).toString(); 124 | int duration = song_query.value(5).toInt(); 125 | Music music(id, name, author, album, picurl, duration); 126 | musicList.push_front(music); 127 | } 128 | return musicList; 129 | } 130 | 131 | bool MusicDB::queryOne(int id) 132 | { 133 | QSqlQuery song_query(m_db); 134 | QString querySql = "SELECT count(*) FROM song WHERE id = :id;"; 135 | song_query.prepare(querySql); 136 | song_query.bindValue(":id", QString::number(id)); 137 | if (!song_query.exec()) { 138 | qDebug() << __FILE__ << __LINE__ << "query error: "< MusicDB::queryList() 206 | { 207 | QSqlQuery song_query(m_db); 208 | QString querySql = "SELECT l_id, l_name, l_cover, l_intro FROM musiclist WHERE l_id >= %1"; 209 | song_query.prepare(querySql.arg(MyListWidget::FAVORITE)); 210 | if (!song_query.exec()) { 211 | qDebug() << __FILE__ << __LINE__ << "query error: "< songList; 215 | while (song_query.next()) { 216 | int id = song_query.value(0).toInt(); 217 | QString name = song_query.value(1).toString(); 218 | QString cover = song_query.value(2).toString(); 219 | QString intro = song_query.value(3).toString(); 220 | songList.push_back(MusicList(id, name, cover, intro)); 221 | } 222 | return songList; 223 | } 224 | 225 | void MusicDB::updateCover(int id, const QString &cover) 226 | { 227 | QSqlQuery song_query(m_db); 228 | QString querySql = "UPDATE musiclist SET l_cover = :cover WHERE l_id = :id"; 229 | song_query.prepare(querySql); 230 | song_query.bindValue(":cover", cover); 231 | song_query.bindValue(":id", id); 232 | if (!song_query.exec()) { 233 | qDebug() << __FILE__ << __LINE__ << "update error: "< 9 | #include 10 | 11 | 12 | class MusicDB 13 | { 14 | public: 15 | MusicDB(); 16 | static MusicDB* instance(); 17 | void open(); 18 | bool exec(const QString& sql); 19 | 20 | void insert(Music &music, int l_id = MyListWidget::FAVORITE); 21 | QList query(int l_id); 22 | bool queryOne(int id); 23 | void remove(int id, int l_id); 24 | 25 | int insertList(const MusicList& list); 26 | QList queryList(); 27 | void updateCover(int id, const QString& cover); 28 | void updateIntro(int id, const QString& intro); 29 | void removeList(int l_id); 30 | void close(); 31 | private: 32 | QSqlDatabase m_db; 33 | }; 34 | 35 | #endif // MUSICDB_H 36 | -------------------------------------------------------------------------------- /musiclist.cpp: -------------------------------------------------------------------------------- 1 | #include "musiclist.h" 2 | 3 | MusicList::MusicList() 4 | { 5 | 6 | } 7 | 8 | MusicList::MusicList(int id, const QString &name, const QString &cover, const QString &intro) : m_id(id), 9 | m_name(name), 10 | m_cover(cover), 11 | m_intro(intro) 12 | {} 13 | 14 | int MusicList::id() const 15 | { 16 | return m_id; 17 | } 18 | 19 | QString MusicList::name() const 20 | { 21 | return m_name; 22 | } 23 | 24 | void MusicList::setName(const QString &newName) 25 | { 26 | m_name = newName; 27 | } 28 | 29 | QString MusicList::cover() const 30 | { 31 | return m_cover; 32 | } 33 | 34 | void MusicList::setCover(const QString &newCover) 35 | { 36 | m_cover = newCover; 37 | } 38 | 39 | QString MusicList::intro() const 40 | { 41 | return m_intro; 42 | } 43 | 44 | void MusicList::setIntro(const QString &newIntro) 45 | { 46 | m_intro = newIntro; 47 | } 48 | 49 | MusicList::MusicList(const QString &name, const QString &cover, const QString &intro) : m_name(name), 50 | m_cover(cover), 51 | m_intro(intro) 52 | {} 53 | -------------------------------------------------------------------------------- /musiclist.h: -------------------------------------------------------------------------------- 1 | #ifndef MUSICLIST_H 2 | #define MUSICLIST_H 3 | 4 | #include 5 | class MusicList 6 | { 7 | public: 8 | MusicList(); 9 | MusicList(const QString &name, const QString &cover = "https://p2.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg", const QString &intro = ""); 10 | MusicList(int id, const QString &name, const QString &cover = "https://p2.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg", const QString &intro = ""); 11 | 12 | int id() const; 13 | QString name() const; 14 | void setName(const QString &newName); 15 | QString cover() const; 16 | void setCover(const QString &newCover); 17 | QString intro() const; 18 | void setIntro(const QString &newIntro); 19 | 20 | private: 21 | int m_id; 22 | QString m_name; 23 | QString m_cover; 24 | QString m_intro; 25 | }; 26 | 27 | // 注册Qariant 28 | Q_DECLARE_METATYPE(MusicList) 29 | 30 | #endif // MUSICLIST_H 31 | -------------------------------------------------------------------------------- /mybottombar.cpp: -------------------------------------------------------------------------------- 1 | #include "mybottombar.h" 2 | #include 3 | 4 | MyBottomBar::MyBottomBar(QWidget *parent) 5 | : QWidget{parent} 6 | { 7 | 8 | } 9 | 10 | void MyBottomBar::mouseReleaseEvent(QMouseEvent *event) 11 | { 12 | if (Qt::LeftButton == event->button()) { 13 | emit clicked(); 14 | } 15 | QWidget::mouseReleaseEvent(event); 16 | } 17 | -------------------------------------------------------------------------------- /mybottombar.h: -------------------------------------------------------------------------------- 1 | #ifndef MYBOTTOMBAR_H 2 | #define MYBOTTOMBAR_H 3 | 4 | #include 5 | 6 | /* 7 | * 主界面底部控制栏 8 | */ 9 | class MyBottomBar : public QWidget 10 | { 11 | Q_OBJECT 12 | public: 13 | explicit MyBottomBar(QWidget *parent = nullptr); 14 | signals: 15 | // 鼠标点击信号 16 | void clicked(); 17 | 18 | protected: 19 | // 鼠标点击事件 20 | void mouseReleaseEvent(QMouseEvent *event); 21 | 22 | }; 23 | 24 | #endif // MYBOTTOMBAR_H 25 | -------------------------------------------------------------------------------- /myhttp.cpp: -------------------------------------------------------------------------------- 1 | #include "myhttp.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | MyHttp::MyHttp(QObject *parent) 10 | : QObject{parent} 11 | { 12 | // 初始化网络请求 13 | request = new QNetworkRequest(); 14 | networkManager = new QNetworkAccessManager(this); 15 | } 16 | 17 | MyHttp::~MyHttp() 18 | { 19 | delete request; 20 | request = nullptr; 21 | } 22 | 23 | /* 24 | * @return 搜索结果(音乐列表) 25 | * @param keywords 搜索关键词 26 | * @param offset:偏移量 27 | * @param limit:返回结果数量 28 | * @param type:搜索类型,取值意义 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 29 | * 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频, 1018:综合, 2000:声音(搜索声音返回字段格式会不一样) 30 | */ 31 | QList MyHttp::search(QString keywords, int offset/* = 0*/, int limit/* = 20*/, int type/* = 1*/) 32 | { 33 | // 清空歌曲id列表和搜索结果列表 34 | musicIdList.clear(); 35 | musicList.clear(); 36 | 37 | // 拼接关键词搜索链接 38 | QUrl url(netease_keywords); 39 | QUrlQuery query; 40 | query.addQueryItem("keywords", keywords); 41 | query.addQueryItem("limit", QString::number(limit)); 42 | query.addQueryItem("offset", QString::number(offset)); 43 | query.addQueryItem("type", QString::number(type)); 44 | url.setQuery(query); 45 | // 搜索关键词并解析返回的Json数据,获取歌曲id列表 46 | searchByUrl(url); 47 | // parseForMusicId(); 48 | 49 | // // 通过歌曲id列表拼接链接 50 | // QString urlStr = QString(netease_songsInfo_Ids); 51 | // for (int i = 0; i < musicIdList.size(); i++) { 52 | // urlStr.append(QString::number(musicIdList.at(i))); 53 | // if (i != musicIdList.size() - 1) 54 | // urlStr.append(","); 55 | // } 56 | // // 搜索并解析返回的Json数据,获取歌曲信息列表 57 | // searchByUrl(QUrl(urlStr)); 58 | parseForMusicInfo(); 59 | // 获取歌曲播放链接 60 | // searchForSongUrls(musicIdList); 61 | return musicList; 62 | } 63 | 64 | // 获取搜索建议 65 | QStringList MyHttp::searchSuggest(QString keywords) 66 | { 67 | QString url = netease_searchSuggest.arg(keywords); 68 | suggest.clear(); 69 | searchByUrl(QUrl(url)); 70 | parseForSuggest(); 71 | return suggest; 72 | } 73 | 74 | // 热搜列表 75 | QStringList MyHttp::hotSearch() 76 | { 77 | hotWords.clear(); 78 | searchByUrl(QUrl(netease_hotSearch)); 79 | parseForHotSearch(); 80 | return hotWords; 81 | } 82 | 83 | QList MyHttp::neteaseHotList() 84 | { 85 | musicList.clear(); 86 | searchByUrl(QUrl(netease_hotSonglist)); 87 | parseForNeteaseHotList(); 88 | return musicList; 89 | } 90 | 91 | /* 92 | *@return 返回歌曲播放链接 93 | *@param id:歌曲id 94 | */ 95 | QString MyHttp::searchForSongUrl(int id, QString level) 96 | { 97 | QUrl url(netease_songUrl_Id); 98 | QUrlQuery query; 99 | query.addQueryItem("id", QString::number(id)); 100 | query.addQueryItem("level", level); 101 | url.setQuery(query); 102 | 103 | searchByUrl(url); 104 | parseForSongUrl(); 105 | return songUrl; 106 | } 107 | 108 | QStringList MyHttp::searchForSongUrls(QVector musicIdList) 109 | { 110 | QString urlStr = QString(netease_songUrl_Id); 111 | for (int i = 0; i < musicIdList.size(); i++) { 112 | urlStr.append(QString::number(musicIdList.at(i))); 113 | if (i != musicIdList.size() - 1) 114 | urlStr.append(","); 115 | } 116 | searchByUrl(urlStr); 117 | parseForSongUrl(); 118 | return musicUrlList; 119 | } 120 | 121 | /* 122 | *@return 返回歌词 123 | *@param id:歌曲id 124 | */ 125 | QMap MyHttp::searchForLrc(int id) 126 | { 127 | QString url = QString(netease_songLrc_Id).arg(id); 128 | searchByUrl(url); 129 | parseForSongLrc(); 130 | return lrcMap; 131 | } 132 | 133 | /* 134 | *@param url:请求链接 135 | *@func 返回的数据存储到QByteArray中 136 | */ 137 | void MyHttp::searchByUrl(QUrl url) 138 | { 139 | // 以get方式请求url 140 | request->setUrl(url); 141 | reply = networkManager->get(*request); 142 | 143 | //阻塞网络请求,请求成功或5秒后没得到响应,则终止阻塞, 144 | QEventLoop eventLoop; 145 | QTimer::singleShot(5000, &eventLoop, 146 | [&]() { 147 | if (eventLoop.isRunning()) { 148 | eventLoop.quit(); 149 | } 150 | }); 151 | connect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit); 152 | eventLoop.exec(QEventLoop::ExcludeUserInputEvents); 153 | 154 | // 状态码 155 | int status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 156 | qDebug() << __FILE__ << __LINE__ << "状态码:" << status_code; 157 | 158 | //response 159 | if (reply->error() == QNetworkReply::NoError) 160 | { 161 | bytes = reply->readAll(); 162 | } 163 | else 164 | { 165 | qDebug()<< __FILE__<<__LINE__<<"searchByUrl_Erro:"<errorString().toUtf8(); 166 | } 167 | reply->deleteLater(); 168 | } 169 | 170 | // 解析QByteArray,得到歌曲id 171 | void MyHttp::parseForMusicId() 172 | { 173 | // 将json解析为编码为UTF-8的json文档 174 | QJsonParseError json_error; 175 | QJsonDocument parse_doucment = QJsonDocument::fromJson(bytes, &json_error); 176 | 177 | // json解析错误处理 178 | if (json_error.error != QJsonParseError::NoError) { 179 | qDebug()<< __FILE__<<__LINE__ <<"parseForMusic_Erro:" << json_error.errorString(); 180 | return; 181 | } 182 | 183 | // 将result下的songs数组提取出来 184 | QJsonArray songsArray = parse_doucment.object().value("result").toObject().value("songs").toArray(); 185 | 186 | int id; // 歌曲id 187 | 188 | // 获取所有歌曲的id 189 | for (int i = 0; i < songsArray.size(); i++) { 190 | // 通过 QJsonArray.at(i)函数获取数组下的第i个元素 191 | QJsonObject song = songsArray.at(i).toObject(); 192 | 193 | // 获取歌曲ID 194 | id = song.value("id").toInt(); 195 | if (0 == id) { 196 | qDebug()<< __FILE__<<__LINE__ << "id解析错误"; 197 | return; 198 | } 199 | musicIdList.push_back(id); 200 | } 201 | } 202 | 203 | // 解析QByteArray,得到歌曲信息,包括歌名、歌手、专辑名、专辑图片链接、歌曲时长 204 | void MyHttp::parseForMusicInfo() 205 | { 206 | // 将json解析为编码为UTF-8的json文档 207 | QJsonParseError json_error; 208 | QJsonDocument parse_doucment = QJsonDocument::fromJson(bytes, &json_error); 209 | 210 | // json解析错误处理 211 | if (json_error.error != QJsonParseError::NoError) { 212 | qDebug()<< __FILE__<<__LINE__ <<"parseForMusicInfo_Erro:" << json_error.errorString(); 213 | return; 214 | } 215 | 216 | // 将 result 下的 songs 数组提取出来 217 | QJsonArray songsArray = parse_doucment.object().value("result").toObject().value("songs").toArray(); 218 | if (songsArray.isEmpty()) { 219 | qDebug()<< __FILE__<<__LINE__ << "songs数组解析错误"; 220 | return; 221 | } 222 | int id; // 歌曲id 223 | QString songName; // 歌名 224 | QSet authors; // 歌手 225 | QString albumName; // 专辑名称 226 | QString albumPicUrl; // 专辑图片链接 227 | int songDuration; // 时长 228 | 229 | // 获取所有歌曲的信息 230 | for (int i = 0; i < songsArray.size(); i++) { 231 | // 通过 QJsonArray.at(i)函数获取数组下的第i个元素 232 | QJsonObject song = songsArray.at(i).toObject(); 233 | 234 | // 获取歌曲id 235 | id = song.value("id").toInt(0); 236 | if(!id) { 237 | qDebug()<< __FILE__<<__LINE__ << "歌曲id为空"; 238 | } 239 | // 获取歌曲名称 240 | songName = song.value("name").toString(); 241 | if (songName.isEmpty()) { 242 | qDebug()<< __FILE__<<__LINE__ << "歌曲名称为空"; 243 | } 244 | 245 | // 获取歌手名称 246 | authors.clear(); 247 | QJsonArray authorArr = song.value("ar").toArray(); 248 | for (int i = 0; i < authorArr.size(); i++) { 249 | authors.insert(authorArr.at(i).toObject().value("name").toString()); 250 | } 251 | 252 | // 获取专辑名称 253 | albumName = song.value("al").toObject().value("name").toString(); 254 | if (albumName.isEmpty()) { 255 | albumName = "未知"; 256 | qDebug()<< __FILE__<<__LINE__ << "专辑名称为空"; 257 | } 258 | 259 | // 获取专辑图片链接 260 | albumPicUrl = song.value("al").toObject().value("picUrl").toString(); 261 | if (albumPicUrl.isEmpty()) { 262 | qDebug()<< __FILE__<<__LINE__ << "专辑图片链接为空"; 263 | } 264 | 265 | // 获取歌曲时长 266 | songDuration = song.value("dt").toInt(0); 267 | if (0 == songDuration) { 268 | qDebug()<< __FILE__<<__LINE__ << "歌曲时长为空"; 269 | } 270 | 271 | // 存储搜索信息 272 | Music music(id, songName, QStringList(authors.begin(), authors.end()).join("、"), albumName, albumPicUrl, songDuration); 273 | musicList.push_back(music); 274 | } 275 | } 276 | 277 | // 解析QByteArray,得到歌曲播放链接 278 | void MyHttp::parseForSongUrl() 279 | { 280 | // 将json解析未编码未UTF-8的json文档 281 | QJsonParseError json_error; // 错误信息 282 | QJsonDocument parse_doucment = QJsonDocument::fromJson(bytes, &json_error); 283 | if (json_error.error != QJsonParseError::NoError) {// 错误处理 284 | qDebug()<<__FILE__<<__LINE__ <<"JsonUrlErro:" << json_error.errorString(); 285 | return; 286 | } 287 | 288 | QJsonArray array = parse_doucment.object().value("data").toArray(); 289 | 290 | // 将data下的url提取出来 291 | 292 | for (int i = 0; i < array.size(); i++) { 293 | int id = array[i].toObject().value("id").toInt(); 294 | songUrl = array[i].toObject().value("url").toString(); 295 | 296 | if (songUrl.isEmpty()) { 297 | // qDebug()<< __FILE__<<__LINE__<< id << "songUrl is empty"; 298 | continue; 299 | } 300 | urlMap.insert(id, songUrl); 301 | } 302 | for (int i = 0; i < musicList.size(); i++) { 303 | QString url = urlMap.value(musicList.at(i).getId()); 304 | qDebug() << __LINE__ << __FILE__ << songUrl; 305 | musicUrlList.push_back(url); 306 | // qDebug()<< __FILE__<<__LINE__<< musicList.at(i).getSongName() << musicList.at(i).getSongUrl(); 307 | } 308 | } 309 | 310 | // 解析QByteArray,得到歌词 311 | void MyHttp::parseForSongLrc() 312 | { 313 | // 将json解析未编码未UTF-8的json文档 314 | QJsonParseError json_error; // 错误信息 315 | QJsonDocument parse_doucment = QJsonDocument::fromJson(bytes, &json_error); 316 | if (json_error.error != QJsonParseError::NoError) {// 错误处理 317 | qDebug()<<__FILE__<<__LINE__ << json_error.errorString(); 318 | return; 319 | } 320 | // 获取歌词 321 | QString lrcs = parse_doucment.object().value("lrc").toObject().value("lyric").toString(); 322 | if (lrcs.isEmpty()) { 323 | qDebug()<< __FILE__<<__LINE__ << "歌词解析错误"; 324 | return; 325 | } 326 | 327 | QStringList lrcList = lrcs.split("\n"); 328 | 329 | lrcMap.clear();// 清理原来的歌词 330 | for (int i = 0; i < lrcList.size(); i++) { 331 | QString str = lrcList.at(i); 332 | QString lrc = str.trimmed(); 333 | // 歌词 334 | QString lrcStr = lrc.mid(lrc.lastIndexOf("]") + 1); 335 | if (lrcStr.isEmpty()) { 336 | continue; 337 | } 338 | //时间解析格式(分*60+秒)*100+厘秒 339 | int min = lrc.midRef(1, 2).toInt(); //分 340 | int sec = lrc.midRef(4, 2).toInt(); //秒 341 | int msec = lrc.midRef(7, 2).toInt(); //毫秒 342 | int lrcTime = (min * 60 + sec) * 1000 + msec; 343 | 344 | //用Qmap来保存 时间和单行歌词 345 | lrcMap.insert(lrcTime, lrcStr); 346 | } 347 | 348 | } 349 | 350 | // 解析QByteArray,得到搜索建议 351 | void MyHttp::parseForSuggest() 352 | { 353 | // 将json解析未编码未UTF-8的json文档 354 | QJsonParseError json_error; // 错误信息 355 | QJsonDocument parse_doucment = QJsonDocument::fromJson(bytes, &json_error); 356 | if (json_error.error != QJsonParseError::NoError) {// 错误处理 357 | qDebug() << __FILE__ << __LINE__ << json_error.errorString(); 358 | return; 359 | } 360 | 361 | QJsonArray array = parse_doucment.object().value("result").toObject().value("allMatch").toArray(); 362 | 363 | // 将result/allMatch下的keyword提取出来 364 | for (int i = 0; i < array.size(); i++) { 365 | // int id = array[i].toObject().value("id").toInt(); 366 | // songUrl = array[i].toObject().value("url").toString(); 367 | QString word = array[i].toObject().value("keyword").toString(); 368 | suggest.append(word); 369 | } 370 | } 371 | 372 | void MyHttp::parseForHotSearch() 373 | { 374 | // 将json解析未编码未UTF-8的json文档 375 | QJsonParseError json_error; // 错误信息 376 | QJsonDocument parse_doucment = QJsonDocument::fromJson(bytes, &json_error); 377 | if (json_error.error != QJsonParseError::NoError) {// 错误处理 378 | qDebug() << __FILE__ << __LINE__ << json_error.errorString(); 379 | return; 380 | } 381 | 382 | QJsonArray array = parse_doucment.object().value("data").toArray(); 383 | 384 | // 将data下的searchWord提取出来 385 | for (int i = 0; i < array.size(); i++) { 386 | QString word = array[i].toObject().value("searchWord").toString(); 387 | hotWords.append(word); 388 | } 389 | } 390 | 391 | void MyHttp::parseForNeteaseHotList() 392 | { 393 | // 将json解析未编码未UTF-8的json文档 394 | QJsonParseError json_error; // 错误信息 395 | QJsonDocument parse_doucment = QJsonDocument::fromJson(bytes, &json_error); 396 | if (json_error.error != QJsonParseError::NoError) {// 错误处理 397 | qDebug() << __FILE__ << __LINE__ << json_error.errorString(); 398 | return; 399 | } 400 | 401 | QJsonArray array = parse_doucment.object().value("songs").toArray(); 402 | // 将songs下的音乐提取出来 403 | for (int i = 0; i < array.size(); i++) { 404 | QJsonObject obj = array.at(i).toObject(); 405 | int id = obj.value("id").toInt(); 406 | QString name = obj.value("name").toString(); 407 | QSet authors; 408 | QJsonArray authorArr = obj.value("ar").toArray(); 409 | for (int i = 0; i < authorArr.size(); i++) { 410 | authors.insert(authorArr.at(i).toObject().value("name").toString()); 411 | } 412 | QString alName = obj.value("al").toObject().value("name").toString(); 413 | QString picUrl = obj.value("al").toObject().value("picUrl").toString(); 414 | int dt = obj.value("dt").toInt(); 415 | musicList.push_back(Music(id, name, QStringList(authors.begin(), authors.end()).join("、"), alName, picUrl, dt)); 416 | } 417 | } 418 | 419 | 420 | -------------------------------------------------------------------------------- /myhttp.h: -------------------------------------------------------------------------------- 1 | #ifndef MYHTTP_H 2 | #define MYHTTP_H 3 | 4 | #include "music.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | /* 13 | * 网络请求处理类 14 | */ 15 | class MyHttp : public QObject 16 | { 17 | Q_OBJECT 18 | public: 19 | explicit MyHttp(QObject *parent = nullptr); 20 | ~MyHttp(); 21 | // 关键词搜索 22 | QList search(QString keywords, int offset = 0, int limit = 80, int type = 1); 23 | // 搜索建议 24 | QStringList searchSuggest(QString keywords); 25 | // 热搜列表 26 | QStringList hotSearch(); 27 | // 获取网易云热歌榜 28 | QList neteaseHotList(); 29 | // 获取单个播放链接 30 | QString searchForSongUrl(int id, QString level = "hires"); 31 | // 获取多个播放链接 32 | QStringList searchForSongUrls(QVector musicIdList); 33 | // 获取歌词 34 | QMap searchForLrc(int id); 35 | 36 | private: 37 | const QString netease_keywords = "https://netease.haohao666.top/cloudsearch"; // 关键词搜索 38 | const QString netease_songsInfo_Ids = "https://netease.haohao666.top/song/detail?ids="; // 通过id获取歌曲信息 39 | const QString netease_songUrl_Id = "https://netease.haohao666.top/song/url/v1"; // 通id获取歌曲播放链接 40 | const QString netease_songLrc_Id = "https://netease.haohao666.top/lyric?id=%1"; // 通过id获取歌词 41 | const QString netease_hotSonglist = "https://netease.haohao666.top/playlist/track/all?id=3778678"; // 网易云热歌榜 42 | const QString netease_hotSearch = "https://netease.haohao666.top/search/hot/detail"; // 搜索建议 43 | const QString netease_searchSuggest = "https://netease.haohao666.top/search/suggest?keywords=%1&type=mobile"; // 搜索建议 44 | QString songUrl = ""; 45 | 46 | QNetworkAccessManager *networkManager; // 网络接口管理 47 | QNetworkRequest *request; // 网络请求 48 | QNetworkReply *reply; // 网络应答 49 | QByteArray bytes; // 返回的字节流数据 50 | 51 | QList musicList; // 搜索结果列表 52 | QVector musicIdList; // 歌曲id列表 53 | QList musicUrlList; // 歌曲链接列表 54 | QMap lrcMap; // 歌词 55 | QMap urlMap; // 歌曲链接 56 | QStringList suggest; // 搜索建议 57 | QStringList hotWords; // 热搜列表 58 | 59 | void searchByUrl(QUrl url); // 链接搜索 60 | void parseForMusicId(); // 解析json获取音乐id列表 61 | void parseForMusicInfo(); // 解析json获取音乐信息列表 62 | void parseForSongUrl(); // 解析json获取音乐播放链接 63 | void parseForSongLrc(); // 解析json获取歌词 64 | void parseForSuggest(); // 解析json获取搜索建议 65 | void parseForHotSearch(); // 解析json获取热搜列表 66 | void parseForNeteaseHotList(); // 解析json获取网易云热歌榜 67 | }; 68 | 69 | #endif // MYHTTP_H 70 | -------------------------------------------------------------------------------- /mylabel.cpp: -------------------------------------------------------------------------------- 1 | #include "mylabel.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "gaussianblur.h" 10 | 11 | MyLabel::MyLabel(QWidget *parent) 12 | : QLabel{parent} 13 | { 14 | // 初始化网络请求 15 | networkManager = new QNetworkAccessManager(this); 16 | request = new QNetworkRequest(); 17 | setShadow(); 18 | 19 | } 20 | 21 | MyLabel::~MyLabel() 22 | { 23 | delete request; 24 | } 25 | 26 | // 获取专辑图片 27 | QPixmap MyLabel::getImg() const 28 | { 29 | if (imgLoaded) 30 | return this->pixmap(Qt::ReturnByValue); 31 | return QPixmap(); 32 | } 33 | 34 | // 设置阴影 35 | void MyLabel::setShadow() 36 | { 37 | //实例阴影shadow 38 | QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect(this); 39 | //设置阴影距离 40 | shadow->setOffset(0, 0); 41 | //设置阴影颜色 42 | shadow->setColor(QColor("#444444")); 43 | //设置阴影长度 44 | shadow->setBlurRadius(5); 45 | //给窗体设置阴影 46 | this->setGraphicsEffect(shadow); 47 | } 48 | 49 | void MyLabel::setBackRole(int show) 50 | { 51 | backRole = show; 52 | } 53 | 54 | void MyLabel::setRadiusPixmap(QString url) 55 | { 56 | if (picUrl == url) 57 | return; 58 | picUrl = url; 59 | int width = this->width(); 60 | url += QString("?param=%1y%2").arg(width).arg(width); 61 | request->setUrl(QUrl(url)); 62 | networkManager->get(*request); 63 | connect(networkManager, &QNetworkAccessManager::finished, this, &MyLabel::onReplyFinished, Qt::UniqueConnection); 64 | } 65 | 66 | //void MyLabel::setRoundedPicInThread(QString url) 67 | //{ 68 | // downLoader->downLoad(url); 69 | // connect(downLoader, &ThreadDownLoader::finished, this, [=](QByteArray bytes) { 70 | // QtConcurrent::run(this, &MyLabel::makeRadiusPixmap, bytes); 71 | // }); 72 | //} 73 | 74 | // 图片下载完成 75 | void MyLabel::onReplyFinished(QNetworkReply *reply) 76 | { 77 | // 获取响应状态码,200表示正常 78 | int status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 79 | qDebug() << __FILE__ << __LINE__ << "状态码:" << status_code; 80 | 81 | if (reply->error() == QNetworkReply::NoError) { 82 | QByteArray bytes = reply->readAll(); //获取字节 83 | if (!backRole) 84 | QtConcurrent::run(this, QOverload::of(&MyLabel::makeRadiusPixmap), bytes); 85 | else 86 | QtConcurrent::run(this, &MyLabel::makeRadiusBlurPixmap, bytes); 87 | }else { 88 | qDebug() << __FILE__ << __LINE__ << "获取专辑图片失败"; 89 | } 90 | reply->deleteLater(); 91 | } 92 | 93 | // 把图片处理为圆角并显示 94 | void MyLabel::makeRadiusPixmap(QByteArray bytes) 95 | { 96 | QPixmap pixmap; 97 | pixmap.loadFromData(bytes); 98 | if (pixmap.isNull()) 99 | return; 100 | //处理大尺寸的图片 101 | pixmap = pixmap.scaledToWidth(this->width()); 102 | makeRadiusPixmap(pixmap); 103 | } 104 | 105 | void MyLabel::makeRadiusBlurPixmap(QByteArray bytes) 106 | { 107 | QImage img; 108 | img.loadFromData(bytes); 109 | GaussianBlur blur; 110 | blur.gaussBlur(img, 300); 111 | makeRadiusPixmap(QPixmap::fromImage(img).scaled(this->size())); 112 | } 113 | 114 | void MyLabel::makeRadiusPixmap(QPixmap pixmap) 115 | { 116 | QSize size = pixmap.size(); 117 | QBitmap mask(size); 118 | QPainter painter(&mask); 119 | painter.setRenderHint(QPainter::Antialiasing); 120 | painter.setRenderHint(QPainter::SmoothPixmapTransform); 121 | painter.fillRect(0, 0, size.width(), size.height(), Qt::white); 122 | painter.setBrush(QColor(0, 0, 0)); 123 | radius = size.width() * 0.1; 124 | if (backRole) 125 | radius = 6; 126 | painter.drawRoundedRect(0, 0, size.width(), size.height(), radius, radius);//修改这个值,可以改弧度,和直径相等就是圆形 127 | pixmap.setMask(mask); 128 | 129 | imgLoaded = true; 130 | // 显示图片 131 | std::lock_guard lck(mutex); 132 | QMetaObject::invokeMethod(this, "setPixmap", Q_ARG(QPixmap, pixmap.scaled(this->size()))); 133 | } 134 | 135 | 136 | -------------------------------------------------------------------------------- /mylabel.h: -------------------------------------------------------------------------------- 1 | #ifndef MYLABEL_H 2 | #define MYLABEL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "threaddownloader.h" 9 | /* 10 | * 用于图片显示的QLabel 11 | */ 12 | class MyLabel : public QLabel 13 | { 14 | Q_OBJECT 15 | public: 16 | explicit MyLabel(QWidget *parent = nullptr); 17 | ~MyLabel(); 18 | // 通过专辑图片链接设置圆角图片 19 | void setRadiusPixmap(QString url); 20 | // 通过专辑图片链接设置圆角图片(多线程) 21 | // void setRoundedPicInThread(QString url); 22 | // 获取专辑图片 23 | QPixmap getImg() const; 24 | // 显示圆角图片 25 | void makeRadiusPixmap(QByteArray bytes); 26 | void makeRadiusBlurPixmap(QByteArray bytes); 27 | void makeRadiusPixmap(QPixmap pixmap); 28 | // 设置图片阴影 29 | void setShadow(); 30 | void setBackRole(int show = true); 31 | private: 32 | QNetworkAccessManager *networkManager = nullptr; // 网络访问管理 33 | QNetworkRequest *request = nullptr; // 网络请求 34 | // ThreadDownLoader *downLoader; // 多线程下载器 35 | QString picUrl = ""; // 专辑图片链接 36 | bool imgLoaded = false; // 图片是否加载完成 37 | std::mutex mutex; 38 | bool backRole = false; 39 | int radius = 0; 40 | private slots: 41 | // 请求完成处理函数 42 | void onReplyFinished(QNetworkReply *reply); 43 | }; 44 | 45 | #endif // MYLABEL_H 46 | -------------------------------------------------------------------------------- /mylineedit.cpp: -------------------------------------------------------------------------------- 1 | #include "mylineedit.h" 2 | #include 3 | 4 | MyLineEdit::MyLineEdit(QWidget *parent) 5 | : QLineEdit{parent} 6 | { 7 | this->setFocus(Qt::MouseFocusReason); 8 | list = nullptr; 9 | } 10 | 11 | void MyLineEdit::setDropDownList(QListWidget *theList) 12 | { 13 | list = theList; 14 | } 15 | 16 | bool MyLineEdit::event(QEvent *e) 17 | { 18 | if (list && e->type() == QEvent::KeyPress) { 19 | QKeyEvent *ke = static_cast(e); 20 | if (ke->key() == Qt::Key_Tab) { 21 | this->setText(list->currentIndex().data(Qt::DisplayRole).toString()); 22 | return true; 23 | } 24 | } 25 | return QLineEdit::event(e); 26 | } 27 | 28 | void MyLineEdit::keyPressEvent(QKeyEvent *event) 29 | { 30 | if (list) { 31 | if (Qt::Key_Up == event->key()) { 32 | int row = list->currentRow(); 33 | if (row) { 34 | list->setCurrentRow(list->currentRow() - 1); 35 | } 36 | return; 37 | } 38 | if (Qt::Key_Down == event->key() ) { 39 | int row = list->currentRow(); 40 | if (row < list->count() - 1) { 41 | list->setCurrentRow(list->currentRow() + 1); 42 | } 43 | return; 44 | } 45 | if (Qt::Key_Return == event->key()) { 46 | QString word = list->currentIndex().data(Qt::DisplayRole).toString(); 47 | if (!word.isEmpty()) 48 | this->setText(word); 49 | this->blockSignals(false); 50 | emit returnPressed(); 51 | return; 52 | } 53 | } 54 | QLineEdit::keyPressEvent(event); 55 | } 56 | 57 | void MyLineEdit::focusInEvent(QFocusEvent *e) 58 | { 59 | emit focusIn(); 60 | setStyleSheet("QLineEdit{color:blue;background-color:#edf2ff;}"); 61 | QLineEdit::focusInEvent(e); 62 | } 63 | 64 | void MyLineEdit::focusOutEvent(QFocusEvent *e) 65 | { 66 | emit focusOut(); 67 | setStyleSheet("QLineEdit{color:black;background-color:#f2f2f4;}"); 68 | QLineEdit::focusOutEvent(e); 69 | } 70 | -------------------------------------------------------------------------------- /mylineedit.h: -------------------------------------------------------------------------------- 1 | #ifndef MYLINEEDIT_H 2 | #define MYLINEEDIT_H 3 | 4 | #include 5 | #include 6 | /* 7 | * 主界面顶部输入框 8 | */ 9 | class MyLineEdit : public QLineEdit 10 | { 11 | Q_OBJECT 12 | public: 13 | explicit MyLineEdit(QWidget *parent = nullptr); 14 | void setDropDownList(QListWidget* theList); 15 | signals: 16 | // 获得焦点信号 17 | void focusIn(); 18 | // 失去焦点信号 19 | void focusOut(); 20 | protected: 21 | bool event(QEvent *e); 22 | void keyPressEvent(QKeyEvent *event); 23 | // 获得焦点事件 24 | void focusInEvent(QFocusEvent * e); 25 | // 失去焦点事件 26 | void focusOutEvent(QFocusEvent *e); 27 | private: 28 | QListWidget *list; 29 | }; 30 | 31 | #endif // MYLINEEDIT_H 32 | -------------------------------------------------------------------------------- /mylistwidget.cpp: -------------------------------------------------------------------------------- 1 | #include "mylistwidget.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | MyListWidget::MyListWidget(QWidget *parent) 8 | : QListWidget{parent} 9 | { 10 | this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 11 | this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 12 | connect(this, &MyListWidget::customItemDoubleClicked, this, &MyListWidget::onCustomItemDoubleClicked); 13 | connect(this->verticalScrollBar(), &QScrollBar::valueChanged, this, &MyListWidget::onScrollBarValueChange); 14 | connect(this->verticalScrollBar(), &QScrollBar::sliderPressed, this, &MyListWidget::onSliderPressed); 15 | connect(this->verticalScrollBar(), &QScrollBar::sliderReleased, this, &MyListWidget::onSliderReleased); 16 | } 17 | 18 | void MyListWidget::init(int id, QString name) 19 | { 20 | this->id = id; 21 | this->name = name; 22 | } 23 | 24 | void MyListWidget::push_back(Music music) 25 | { 26 | m_musicList.push_back(music); 27 | // insertCustomItem(music, m_musicList.size() - 1); 28 | } 29 | 30 | void MyListWidget::push_front(Music music) 31 | { 32 | insertCustomItem(music, 0); 33 | if (itemsNum > 0) 34 | itemsNum++; 35 | // m_musicList.push_front(music); 36 | 37 | } 38 | 39 | void MyListWidget::pop_front() 40 | { 41 | if (!m_musicList.empty()) { 42 | removeCustomItem(0); 43 | // m_musicList.pop_front(); 44 | } 45 | } 46 | 47 | void MyListWidget::pop_back() 48 | { 49 | if (itemsNum == m_musicList.size()) { 50 | removeCustomItem(m_musicList.size() - 1); 51 | } 52 | // m_musicList.pop_back(); 53 | } 54 | 55 | void MyListWidget::takeAt(int index) 56 | { 57 | if (index < itemsNum) 58 | removeCustomItem(index); 59 | // m_musicList.takeAt(index); 60 | } 61 | 62 | // 设置音乐列表,并加载前10项 63 | void MyListWidget::setMusicList(const QList &musicList) 64 | { 65 | // 清空列表并滚动到顶部 66 | items.clear(); 67 | this->clearList(); 68 | this->scrollToTop(); 69 | // 更新音乐列表,加载前10项 70 | m_musicList.clear(); 71 | m_musicList = musicList; 72 | itemsNum = 10; 73 | if (itemsNum > musicList.size()) 74 | itemsNum = musicList.size(); 75 | addCustomItems(0, itemsNum); 76 | loadmore = true; 77 | emit loaded(id); 78 | } 79 | 80 | // 插入item 81 | void MyListWidget::insertCustomItem(Music music, int row) 82 | { 83 | // 如果插入位置正常 84 | if (row >= 0 && (row <= itemsNum)) { 85 | addCustomItem(music, row); 86 | m_musicList.insert(row, music); 87 | } 88 | } 89 | 90 | void MyListWidget::removeCustomItem(CustomItem *item) 91 | { 92 | if (!doubleClickedItem && item == doubleClickedItem) { 93 | emit clear(); 94 | } 95 | int index = items.indexOf(item); 96 | removeCustomItem(index); 97 | } 98 | 99 | void MyListWidget::removeCustomItem(int index) 100 | { 101 | m_musicList.takeAt(index); 102 | if (index + 1 <= itemsNum) { 103 | itemsNum--; 104 | } 105 | items.removeAt(index); 106 | QListWidgetItem *lItem = this->takeItem(index); 107 | delete lItem; 108 | if (itemsNum < 10) { 109 | loadMore(); 110 | } 111 | 112 | } 113 | 114 | int MyListWidget::getListType() 115 | { 116 | return id; 117 | } 118 | 119 | // 当滚动到底部,加载更多项 120 | void MyListWidget::onScrollBarValueChange(int value) 121 | { 122 | if (loadmore && value >= this->verticalScrollBar()->maximum() - 20) { 123 | loadMore(); 124 | } 125 | // 如果鼠标滚轮滚动切滚动条处于隐藏状态,打开滚动条,几秒后关闭 126 | if (!scrollBarOn && value != verticalBarValue) { 127 | this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); 128 | scrollBarOn = true; 129 | QTimer::singleShot(4000, [=]{ 130 | if (sliderPressed) 131 | return; 132 | scrollBarOn = false; 133 | this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 134 | }); 135 | } 136 | } 137 | 138 | void MyListWidget::onCustomItemDoubleClicked(CustomItem *item) 139 | { 140 | doubleClickedItem = item; 141 | } 142 | 143 | void MyListWidget::onSliderPressed() 144 | { 145 | sliderPressed = true; 146 | } 147 | 148 | void MyListWidget::onSliderReleased() 149 | { 150 | sliderPressed = false; 151 | QTimer::singleShot(4000, [=]{ 152 | if (sliderPressed) 153 | return; 154 | scrollBarOn = false; 155 | this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 156 | }); 157 | } 158 | 159 | // 加载更多 160 | void MyListWidget::loadMore() 161 | { 162 | // 当没有item可加载时返回 163 | if (itemsNum >= m_musicList.size()) 164 | return; 165 | // 需加载的第一项 166 | int begin = itemsNum; 167 | // 需加载的最后一项 168 | int end = itemsNum + 4; 169 | // item数量不大于音乐数量 170 | if (end > m_musicList.size()) 171 | end = m_musicList.size(); 172 | // 添加item 173 | addCustomItems(begin, end); 174 | itemsNum = end; 175 | qDebug() << __FILE__ << __LINE__ << "load more" << begin << end; 176 | } 177 | 178 | // 清空列表 179 | void MyListWidget::clearList() 180 | { 181 | loadmore = false; 182 | this->clear(); 183 | emit cleared(id); 184 | } 185 | 186 | void MyListWidget::mouseReleaseEvent(QMouseEvent *event) 187 | { 188 | QListWidget::mouseReleaseEvent(event); 189 | QWidget::mouseReleaseEvent(event); 190 | } 191 | 192 | // 加载自定义items 193 | void MyListWidget::addCustomItems(int begin, int end) 194 | { 195 | for (int i = begin; i < end; i++) { 196 | Music music = m_musicList.at(i); 197 | addCustomItem(music); 198 | } 199 | } 200 | 201 | // 加载自定义item 202 | void MyListWidget::addCustomItem(const Music music, int row/* = -1*/) 203 | { 204 | QListWidgetItem* qItem = new QListWidgetItem(); 205 | if (row == -1) 206 | this->addItem(qItem); 207 | else 208 | this->insertItem(row, qItem); 209 | // itemsNum++; 210 | qItem->setSizeHint(QSize(0, 80)); 211 | 212 | CustomItem *myItem = new CustomItem(music); 213 | if (row == 0) 214 | items.push_front(myItem); 215 | else 216 | items.push_back(myItem); 217 | myItem->setItemType(id); 218 | this->setItemWidget(qItem, myItem); 219 | connect(myItem, &CustomItem::myItemDoubleClicked, this, &MyListWidget::customItemDoubleClicked); 220 | connect(myItem, &CustomItem::menuClicked, this, &MyListWidget::menuClicked); 221 | } 222 | -------------------------------------------------------------------------------- /mylistwidget.h: -------------------------------------------------------------------------------- 1 | #ifndef MYLISTWIDGET_H 2 | #define MYLISTWIDGET_H 3 | 4 | #include "music.h" 5 | #include "customitem.h" 6 | #include 7 | /* 8 | * 搜索列表、我喜欢的音乐列表、歌单列表等用于音乐显示的列表 9 | */ 10 | class MyListWidget : public QListWidget 11 | { 12 | Q_OBJECT 13 | public: 14 | explicit MyListWidget(QWidget *parent = nullptr); 15 | // 列表类型 16 | enum ListType { 17 | SEARCHRESULT, 18 | LOCAL, 19 | HISTORY, 20 | FAVORITE, 21 | DEFALUT 22 | }; 23 | Q_ENUM(ListType) 24 | void init (int id, QString name); 25 | void push_back(Music music); 26 | void push_front(Music music); 27 | void pop_front(); 28 | void pop_back(); 29 | void takeAt(int index); 30 | // 设置音乐列表 31 | void setMusicList(const QList &musicList); 32 | void insertCustomItem(Music music, int row = 0); 33 | void removeCustomItem(CustomItem *item); 34 | void removeCustomItem(int index); 35 | // 获取列表类型 36 | int getListType(); 37 | 38 | signals: 39 | // item双击信号 40 | void customItemDoubleClicked(CustomItem *item); 41 | // 菜单点击信号 42 | void menuClicked(CustomItem *item, int itemType); 43 | void loaded(int id); 44 | void cleared(int id); 45 | private: 46 | // 添加item 47 | void addCustomItem(const Music music, int row = -1); 48 | // 添加items 49 | void addCustomItems(int begin, int end); 50 | // 加载更多 51 | void loadMore(); 52 | // 清空列表 53 | void clearList(); 54 | protected: 55 | void mouseReleaseEvent(QMouseEvent *event); 56 | private slots: 57 | void onScrollBarValueChange(int value); 58 | void onCustomItemDoubleClicked(CustomItem *item); 59 | void onSliderPressed(); 60 | void onSliderReleased(); 61 | private: 62 | int id; // 列表id 63 | QString name; // 歌单名 64 | 65 | int verticalBarValue = 0; // 垂直滚动条数值 66 | bool scrollBarOn = false; // 垂直滚动条是否可见 67 | bool sliderPressed = false; // 垂直滚动条是否按下 68 | 69 | int itemsNum; // 歌曲数 70 | bool loadmore = true; // 是否可以加载更多 71 | 72 | QList m_musicList; // 音乐列表 73 | QList items; // 自定义的items 74 | CustomItem *doubleClickedItem = nullptr; // 被双击的item 75 | }; 76 | 77 | #endif // MYLISTWIDGET_H 78 | -------------------------------------------------------------------------------- /mymediaplaylist.cpp: -------------------------------------------------------------------------------- 1 | #include "mymediaplaylist.h" 2 | #include 3 | #include 4 | MyMediaPlaylist::MyMediaPlaylist(QObject *parent) 5 | : QMediaPlaylist {parent} 6 | { 7 | search = new MyHttp(this); 8 | 9 | } 10 | 11 | // 播放列表音乐数量 12 | int MyMediaPlaylist::mediaCount() const 13 | { 14 | return m_idList.size(); 15 | } 16 | 17 | // 通过播放列表索引获取音乐 18 | QMediaContent MyMediaPlaylist::media(int index) const 19 | { 20 | qDebug() << __FILE__ << __LINE__ << "media"; 21 | if (index < 0 || index >= m_idList.size()) 22 | return QMediaContent(); 23 | int id = m_idList.at(index); 24 | QString url = search->searchForSongUrl(id); 25 | qDebug() << __FILE__ << __LINE__ << url; 26 | return QMediaContent(QUrl(url)); 27 | } 28 | 29 | // 当前索引对应音乐 30 | QMediaContent MyMediaPlaylist::currentMedia() const 31 | { 32 | return media(m_currentIndex); 33 | } 34 | 35 | int MyMediaPlaylist::musicIndex(int id) 36 | { 37 | return m_idList.indexOf(id); 38 | } 39 | 40 | // 当前音乐在播放列表中的序号 41 | int MyMediaPlaylist::currentIndex() const 42 | { 43 | return m_currentIndex; 44 | } 45 | 46 | // 设置当前播放的音乐 47 | void MyMediaPlaylist::setCurrentIndex(int index) 48 | { 49 | qDebug() << __FILE__ << __LINE__ << "setCurrentIndex"; 50 | // 判断索引是否合法 51 | if (index < 0 || index >= m_idList.size()) 52 | return; 53 | if (index == m_currentIndex) 54 | return; 55 | // 更新当前索引 56 | m_currentIndex = index; 57 | // 加入历史播放列表 58 | if (m_recentIndex.size() > 50) 59 | m_recentIndex.removeFirst(); 60 | m_recentIndex.push_back(index); 61 | // 通过索引获取当前音乐id,在此基础上获取音乐播放链接 62 | int id = m_idList.at(index); 63 | QString url = search->searchForSongUrl(id); 64 | // 若播放链接为空,播放下一首 65 | if (url.isEmpty()) { 66 | qDebug() << __FILE__ << __LINE__ << "song url is null"; 67 | setCurrentIndex(m_currentIndex + 1); 68 | return; 69 | } 70 | Q_EMIT currentMediaChanged(QMediaContent(url)); 71 | } 72 | 73 | // 获取steps首后的音乐索引 74 | int MyMediaPlaylist::nextIndex(int steps) 75 | { 76 | qDebug() << __FILE__ << __LINE__ << "nextIndex"; 77 | int next = m_currentIndex; 78 | // 根据播放模式,确定下一首播放的音乐 79 | switch (playbackMode()) { 80 | case QMediaPlaylist::Random: // 随机播放 81 | // 如果随机播放列表为空,则获取随机播放列表 82 | if (m_shuffleList.isEmpty()) { 83 | shuffle(); 84 | qDebug() << __FILE__ << __LINE__ << "shuffle"; 85 | } 86 | next = m_shuffleList.takeFirst(); 87 | break; 88 | case QMediaPlaylist::Sequential: // 顺序播放 89 | next = m_currentIndex + steps; 90 | if (next >= m_idList.size() - 1) 91 | return -1; 92 | break; 93 | case QMediaPlaylist::Loop: // 列表循环 94 | next = m_currentIndex + steps; 95 | if (next >= m_idList.size() - 1) 96 | return 0; 97 | break; 98 | case QMediaPlaylist::CurrentItemInLoop: // 单曲循环 99 | return m_currentIndex; 100 | break; 101 | default: 102 | break; 103 | } 104 | return next; 105 | } 106 | 107 | // 获取前steps首的音乐索引 108 | int MyMediaPlaylist::previousIndex(int steps) 109 | { 110 | qDebug() << __FILE__ << __LINE__ << "previousIndex"; 111 | int previous = m_currentIndex; 112 | // 根据播放模式,确定下一首播放的音乐 113 | switch (playbackMode()) { 114 | case QMediaPlaylist::Random: // 随机播放 115 | if (!m_recentIndex.isEmpty()) { 116 | while (steps-- > 0) { 117 | m_recentIndex.pop_back(); 118 | } 119 | return m_recentIndex.takeLast(); 120 | } else { 121 | return -1; 122 | } 123 | break; 124 | case QMediaPlaylist::Sequential: // 顺序播放 125 | previous = m_currentIndex - steps; 126 | if (previous >= m_idList.size()) 127 | return -1; 128 | break; 129 | case QMediaPlaylist::Loop: // 列表循环 130 | previous = m_currentIndex - steps; 131 | if (previous < 0) 132 | return m_idList.size() - 1; 133 | break; 134 | case QMediaPlaylist::CurrentItemInLoop: // 单曲循环 135 | return m_currentIndex; 136 | break; 137 | default: 138 | break; 139 | } 140 | return previous; 141 | } 142 | 143 | // 雪花算法,生成随机播放列表 144 | void MyMediaPlaylist::shuffle() 145 | { 146 | // 清空随机列表 147 | m_shuffleList.clear(); 148 | // 生成与id列表等长的递增数列,0,1,2,3.... 149 | int count = m_idList.size(); 150 | for (int i = 0; i < count; i++) { 151 | m_shuffleList.append(i); 152 | } 153 | // 打乱递增数列的顺序 154 | std::random_shuffle(m_shuffleList.begin(), m_shuffleList.end()); 155 | m_shuffleList.swapItemsAt(0, m_shuffleList.size()>>1); 156 | } 157 | 158 | // 在最后添加音乐 159 | bool MyMediaPlaylist::addMedia(int id) 160 | { 161 | m_idList.push_back(id); 162 | Q_EMIT mediaInserted(m_idList.size() - 1, m_idList.size() - 1); 163 | return true; 164 | } 165 | 166 | // 指定位置插入音乐 167 | bool MyMediaPlaylist::insertMedia(int index, const int id) 168 | { 169 | qDebug() << __FILE__ << __LINE__ << "insertMedia"; 170 | // 索引合法性检验 171 | if (index < 0 || index >= m_idList.size()) 172 | return false; 173 | m_idList.insert(index, id); 174 | // 刷新随机列表 175 | if (playbackMode() == QMediaPlaylist::Random) { 176 | shuffle(); 177 | } 178 | // 更新当前音乐index 179 | if (index <= m_currentIndex) { 180 | m_currentIndex++; 181 | } 182 | // Q_EMIT mediaInserted(index, index); 183 | return true; 184 | } 185 | 186 | // 删除音乐 187 | bool MyMediaPlaylist::removeMedia(int index) 188 | { 189 | qDebug() << __FILE__ << __LINE__ << "removeMedia"; 190 | // 索引合法性检验 191 | if (index < 0 || index >= m_idList.size()) 192 | return false; 193 | // 移除指定音乐 194 | m_idList.removeAt(index); 195 | // 刷新随机列表 196 | if (playbackMode() == QMediaPlaylist::Random) { 197 | shuffle(); 198 | } 199 | // 更新当前音乐index 200 | if (index <= m_currentIndex) { 201 | m_currentIndex--; 202 | } 203 | // Q_EMIT mediaRemoved(index, index); 204 | return true; 205 | } 206 | 207 | // 清空播放列表 208 | bool MyMediaPlaylist::clear() 209 | { 210 | qDebug() << __FILE__ << __LINE__ << "clear"; 211 | if (!m_idList.isEmpty()) 212 | m_idList.clear(); // 清空id列表 213 | if (!m_shuffleList.isEmpty()) 214 | m_shuffleList.clear(); // 清空随机播放列表 215 | if (!m_recentIndex.isEmpty()) { 216 | m_recentIndex.clear(); 217 | } 218 | m_currentIndex = -1; 219 | return true; 220 | } 221 | 222 | // 播放上一首 223 | void MyMediaPlaylist::last() 224 | { 225 | setCurrentIndex(previousIndex()); 226 | } 227 | 228 | // 播放下一首 229 | void MyMediaPlaylist::next() 230 | { 231 | qDebug() << __FILE__ << __LINE__ << "next"; 232 | setCurrentIndex(nextIndex()); 233 | } 234 | -------------------------------------------------------------------------------- /mymediaplaylist.h: -------------------------------------------------------------------------------- 1 | #ifndef MYMEDIAPLAYLIST_H 2 | #define MYMEDIAPLAYLIST_H 3 | 4 | #include 5 | #include "myhttp.h" 6 | 7 | class MyMediaPlaylist : public QMediaPlaylist 8 | { 9 | Q_OBJECT 10 | public: 11 | explicit MyMediaPlaylist(QObject *parent = nullptr); 12 | int mediaCount() const; 13 | QMediaContent media(int index) const; 14 | QMediaContent currentMedia() const; 15 | int musicIndex(int id); 16 | int currentIndex() const; 17 | void setCurrentIndex(int index); 18 | int nextIndex(int steps = 1); 19 | int previousIndex(int steps = 1); 20 | void shuffle(); 21 | bool addMedia(int id); 22 | bool insertMedia(int index, const int id); 23 | bool removeMedia(int index); 24 | bool clear(); 25 | void last(); 26 | void next(); 27 | private: 28 | QList m_idList; 29 | int m_currentIndex = -1; 30 | MyHttp *search; 31 | QList m_shuffleList; 32 | QList m_recentIndex; 33 | }; 34 | 35 | #endif // MYMEDIAPLAYLIST_H 36 | -------------------------------------------------------------------------------- /res.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | icon/close.svg 4 | icon/folder-open.svg 5 | icon/home.svg 6 | icon/max.svg 7 | icon/Max2normal.svg 8 | icon/min.svg 9 | icon/Music.svg 10 | icon/music_last.svg 11 | icon/music_next.svg 12 | icon/MusicPlayer.ico 13 | icon/order-play-fill.svg 14 | icon/Player, pause.svg 15 | icon/Player, play.svg 16 | icon/player_paly.svg 17 | icon/player-list-add.svg 18 | icon/player-pause.svg 19 | icon/player-skip-back.svg 20 | icon/player-skip-forward.svg 21 | icon/queue_music.svg 22 | icon/random.svg 23 | icon/repeat.svg 24 | icon/repeat-one-line.svg 25 | icon/search.svg 26 | icon/volume.svg 27 | icon/noVolume.svg 28 | icon/pkq.jpg 29 | icon/loading.gif 30 | icon/downArow_black.svg 31 | icon/downArow_gray.svg 32 | icon/add.svg 33 | icon/rounded .svg 34 | 35 | 36 | style.css 37 | 38 | 39 | -------------------------------------------------------------------------------- /searchtipslist.cpp: -------------------------------------------------------------------------------- 1 | #include "searchtipslist.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | SearchTipsList::SearchTipsList(QWidget *parent) 9 | : QListWidget{parent} 10 | { 11 | this->setObjectName("searchTips"); 12 | this->setMaximumHeight(600); 13 | this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 14 | aniDuration = 400; 15 | // 阴影 16 | this->setContentsMargins(11, 11, 11, 11); 17 | QGraphicsDropShadowEffect *shadow_effect = new QGraphicsDropShadowEffect(this); 18 | //窗口添加对应的阴影效果 19 | shadow_effect->setOffset(0, 0); 20 | shadow_effect->setColor(QColor(150,150,150)); 21 | shadow_effect->setBlurRadius(6); 22 | this->setGraphicsEffect(shadow_effect); 23 | } 24 | 25 | void SearchTipsList::updateSize() 26 | { 27 | this->setMaximumHeight(this->sizeHintForRow(0) * 8.5 + 2 * this->frameWidth()); 28 | this->resize(this->sizeHint()); 29 | } 30 | 31 | void SearchTipsList::showTips() 32 | { 33 | this->resize(this->width(), 0); 34 | this->show(); 35 | animation(true); 36 | } 37 | 38 | void SearchTipsList::hideTips() 39 | { 40 | animation(false); 41 | } 42 | 43 | QSize SearchTipsList::sizeHint() 44 | { 45 | return QSize(this->sizeHintForColumn(0) + 2 * this->frameWidth(), this->sizeHintForRow(0) * this->count()/* + 2 * this->frameWidth()*/); 46 | } 47 | 48 | void SearchTipsList::animation(bool show) 49 | { 50 | QPropertyAnimation* ani = new QPropertyAnimation(this, "size"); 51 | ani->setStartValue(QSize(this->width(), 0)); // 高度为0 52 | ani->setEndValue(sizeHint()); // 高度正常 53 | ani->setEasingCurve(QEasingCurve::OutQuad); // 变化曲线 54 | ani->setDuration(aniDuration); // 动画时长 55 | if (show) { // 展开动画 56 | ani->setDirection(QAbstractAnimation::Forward); 57 | } else { // 隐藏动画 58 | ani->setDirection(QAbstractAnimation::Backward); 59 | connect(ani, &QPropertyAnimation::finished, this, &SearchTipsList::hide); 60 | } 61 | // 开始动画 62 | ani->start(QAbstractAnimation::DeleteWhenStopped); 63 | } 64 | 65 | 66 | -------------------------------------------------------------------------------- /searchtipslist.h: -------------------------------------------------------------------------------- 1 | #ifndef SEARCHTIPSLIST_H 2 | #define SEARCHTIPSLIST_H 3 | 4 | #include 5 | 6 | class SearchTipsList : public QListWidget 7 | { 8 | Q_OBJECT 9 | public: 10 | explicit SearchTipsList(QWidget *parent = nullptr); 11 | // 根据item数量改变列表大小 12 | void updateSize(); 13 | // 显示搜索提示 14 | void showTips(); 15 | // 隐藏搜索提示 16 | void hideTips(); 17 | protected: 18 | QSize sizeHint(); 19 | private: 20 | void animation(bool show = true); 21 | private: 22 | int aniDuration; 23 | }; 24 | 25 | #endif // SEARCHTIPSLIST_H 26 | -------------------------------------------------------------------------------- /shadowwidget.cpp: -------------------------------------------------------------------------------- 1 | #include "shadowwidget.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | ShadowWidget::ShadowWidget(QWidget *parent) 10 | : QWidget{parent} 11 | { 12 | initWidget(); 13 | } 14 | 15 | // 初始化窗口 16 | void ShadowWidget::initWidget() 17 | { 18 | //TODO: 设置窗口无边框/背景透明属性 19 | setWindowFlags(this->windowFlags() | Qt::FramelessWindowHint);//无边框的窗口 20 | setAttribute(Qt::WA_TranslucentBackground);//实现圆角的无边框窗口必须设置的属性 21 | 22 | //TODO: 初始化阴影的渐变值 23 | qreal factor = m_alphaValue / m_radius; 24 | for (int i = 0; i < m_radius; i++) 25 | { 26 | int level = (int)(m_alphaValue - qSqrt(i) * factor + 0.5); 27 | m_blurValue.push_back(qMax(0, level - 2)); 28 | } 29 | } 30 | 31 | // 界面平移切换动画 32 | void ShadowWidget::siwtchingAnimation(QWidget *parent ,QWidget *leftWidget, QWidget *rightWidget, int direction) 33 | { 34 | // 初始化动画 35 | if (m_animGroup == nullptr) { 36 | // 初始化两个label,用来存储两个界面的截图 37 | m_leftLabel = new QLabel(parent); 38 | m_leftLabel->resize(leftWidget->size()); 39 | m_leftLabel->raise(); 40 | 41 | m_rightLabel = new QLabel(parent); 42 | m_rightLabel->resize(rightWidget->size()); 43 | m_rightLabel->raise(); 44 | // 设置左界面动画 45 | QPropertyAnimation *animLeft = new QPropertyAnimation(m_leftLabel, "geometry"); 46 | animLeft->setDuration(animTime); 47 | animLeft->setStartValue(leftWidget->geometry()); 48 | 49 | 50 | animLeft->setEasingCurve(QEasingCurve::OutCubic); 51 | // 设置右界面动画 52 | QPropertyAnimation *animRight = new QPropertyAnimation(m_rightLabel, "geometry"); 53 | animRight->setDuration(animTime); 54 | 55 | animRight->setEndValue(rightWidget->geometry()); 56 | animRight->setEasingCurve(QEasingCurve::OutCubic); 57 | 58 | switch (direction) { 59 | case AnimDirection::Forward: 60 | case AnimDirection::Backward: 61 | animLeft->setEndValue(QRect(-(leftWidget->width()), 0, leftWidget->width(), leftWidget->height())); 62 | animRight->setStartValue(QRect(rightWidget->width(), 0, 0, rightWidget->height())); 63 | break; 64 | case AnimDirection::Up: 65 | case AnimDirection::Down: 66 | animLeft->setEndValue(QRect(0, -(leftWidget->height()), leftWidget->width(), leftWidget->height())); 67 | animRight->setStartValue(QRect(0, rightWidget->height(), rightWidget->width(), rightWidget->height())); 68 | break; 69 | } 70 | // 将两个界面动画添加到并行动画组 71 | m_animGroup = new QParallelAnimationGroup(this); 72 | m_animGroup->addAnimation(animLeft); 73 | m_animGroup->addAnimation(animRight); 74 | } 75 | 76 | // 用两个label存储两个界面的截图 77 | m_leftLabel->setPixmap(leftWidget->grab()); 78 | m_rightLabel->setPixmap(rightWidget->grab()); 79 | // 显示label 80 | m_leftLabel->show(); 81 | m_rightLabel->show(); 82 | // 提升label 83 | m_leftLabel->raise(); 84 | m_rightLabel->raise(); 85 | 86 | // 选择动画的方向 87 | if (AnimDirection::Forward == direction) { 88 | m_animGroup->setDirection(QAbstractAnimation::Forward); 89 | } 90 | if (AnimDirection::Backward == direction) { 91 | m_animGroup->setDirection(QAbstractAnimation::Backward); 92 | } 93 | switch (direction) { 94 | case AnimDirection::Forward: 95 | case AnimDirection::Up: 96 | m_animGroup->setDirection(QAbstractAnimation::Forward); 97 | break; 98 | case AnimDirection::Down: 99 | case AnimDirection::Backward: 100 | m_animGroup->setDirection(QAbstractAnimation::Backward); 101 | break; 102 | } 103 | 104 | // 播放动画,动画结束隐藏label 105 | m_animGroup->start(QAbstractAnimation::KeepWhenStopped); 106 | connect(m_animGroup, &QParallelAnimationGroup::finished, [=] { 107 | m_leftLabel->hide(); 108 | m_rightLabel->hide(); 109 | }); 110 | } 111 | 112 | // 绘图事件 113 | void ShadowWidget::paintEvent(QPaintEvent *event) 114 | { 115 | Q_UNUSED(event); 116 | QPainter painter(this); 117 | //TODO: 先填充背景,原来是透明的,用背景颜色进行填充 118 | QPainterPath path; 119 | path.setFillRule(Qt::WindingFill); 120 | path.addRoundedRect(m_radius, m_radius, width() - m_radius * 2, 121 | height() - m_radius * 2, m_radius, m_radius); 122 | painter.setRenderHint(QPainter::Antialiasing, true); 123 | painter.fillPath(path, QBrush(m_backgroundColor)); 124 | 125 | //TODO: 再绘制圆角矩形path,利用渐变的颜色。 126 | auto color = m_shadowColor; 127 | for (int i = 0; i < m_radius; i++) 128 | { 129 | QPainterPath path; 130 | path.setFillRule(Qt::WindingFill); // 缠绕填充 131 | path.addRoundedRect(m_radius - i, m_radius - i, width() - (m_radius - i) * 2, 132 | height() - (m_radius - i) * 2, m_radius, m_radius); 133 | 134 | int alpha = 0; 135 | if (i < m_blurValue.size()) 136 | { 137 | alpha = m_blurValue[i]; 138 | } 139 | color.setAlpha(alpha); 140 | painter.setPen(color); 141 | painter.drawPath(path); 142 | } 143 | 144 | } 145 | 146 | // 鼠标移动事件 147 | void ShadowWidget::mouseMoveEvent(QMouseEvent *event) 148 | { 149 | if (m_lefted) 150 | { 151 | //TODO: 鼠标移动时计算 (当前鼠标的位置- 按下鼠标位置的差= 窗口移动的距离) 152 | QPoint offset = event->pos() - m_leftPoint; 153 | move(offset + pos()); 154 | } 155 | } 156 | 157 | // 鼠标按下事件 158 | void ShadowWidget::mousePressEvent(QMouseEvent *event) 159 | { 160 | this->setFocus(Qt::MouseFocusReason); 161 | if (event->button() == Qt::LeftButton) 162 | { 163 | //TODO: 记录鼠标按下时的位置(QPoint) 164 | m_lefted = true; 165 | m_leftPoint = event->pos(); 166 | } 167 | } 168 | 169 | // 鼠标释放事件 170 | void ShadowWidget::mouseReleaseEvent(QMouseEvent *event) 171 | { 172 | if (event->button() == Qt::LeftButton) 173 | { 174 | //TODO: 鼠标释放时重置下记录的数据 175 | m_lefted = false; 176 | m_leftPoint.setX(0); 177 | m_leftPoint.setY(0); 178 | } 179 | } 180 | 181 | 182 | -------------------------------------------------------------------------------- /shadowwidget.h: -------------------------------------------------------------------------------- 1 | #ifndef SHADOWWIDGET_H 2 | #define SHADOWWIDGET_H 3 | 4 | #include 5 | #include 6 | 7 | class ShadowWidget : public QWidget 8 | { 9 | Q_OBJECT 10 | public: 11 | enum AnimDirection { 12 | Forward, 13 | Backward, 14 | Up, 15 | Down 16 | }; 17 | explicit ShadowWidget(QWidget *parent = nullptr); 18 | protected: 19 | // 重写绘图事件,实现圆角阴影窗口 20 | void paintEvent(QPaintEvent *event); 21 | // 重写鼠标按下、移动、释放事件,实现窗口跟随鼠标拖动 22 | void mouseMoveEvent(QMouseEvent *event); 23 | void mousePressEvent(QMouseEvent *event); 24 | void mouseReleaseEvent(QMouseEvent *event); 25 | 26 | /* 27 | * @function: 实现两个窗口的平移切换动画 28 | * @param: parent 两个窗口的父窗口,一般是QStackWidget 29 | * @param: leftWidget 左窗口 30 | * @param: rigjtWidget 右窗口 31 | * @param: direction 平移的方向 32 | */ 33 | void siwtchingAnimation(QWidget *parent, QWidget * leftWidget, QWidget *rightWidget, int direction = AnimDirection::Forward); 34 | 35 | protected: 36 | int animTime = 600; 37 | private: 38 | // 初始化窗口 39 | void initWidget(); 40 | 41 | private: 42 | int m_alphaValue = 50; //透明度 43 | int m_radius = 6; //默认窗口的圆角半径 44 | QVector m_blurValue; //阴影渐变的颜色 45 | QColor m_shadowColor = QColor("#65666e"); //阴影的颜色 46 | QColor m_backgroundColor = Qt::white; //窗口的背景颜色 47 | 48 | //能够移动窗口的属性 49 | bool m_lefted = false; 50 | QPoint m_leftPoint; 51 | 52 | // 界面平移切换动画 53 | QLabel *m_leftLabel = nullptr; 54 | QLabel *m_rightLabel = nullptr; 55 | QParallelAnimationGroup *m_animGroup = nullptr; 56 | }; 57 | 58 | #endif // SHADOWWIDGET_H 59 | -------------------------------------------------------------------------------- /static/lrc_Page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhyxy/MusicPlayer-in-Qt-Plus/75899ecc9480d4b3b207fcddfa1c0abced6411eb/static/lrc_Page.png -------------------------------------------------------------------------------- /static/lrc_Page_gaussionBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhyxy/MusicPlayer-in-Qt-Plus/75899ecc9480d4b3b207fcddfa1c0abced6411eb/static/lrc_Page_gaussionBackground.png -------------------------------------------------------------------------------- /static/menu_buttonRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhyxy/MusicPlayer-in-Qt-Plus/75899ecc9480d4b3b207fcddfa1c0abced6411eb/static/menu_buttonRight.png -------------------------------------------------------------------------------- /static/searchResult_Page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhhyxy/MusicPlayer-in-Qt-Plus/75899ecc9480d4b3b207fcddfa1c0abced6411eb/static/searchResult_Page.png -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | * { 2 | border: none; 3 | font-family:"华文楷体" ; 4 | font: 20pt ; 5 | font-size: 20px; 6 | text-align: center; 7 | } 8 | 9 | QTabBar::tab { 10 | width: 0; 11 | height:0; 12 | } 13 | 14 | QMenu { 15 | margin:8px; 16 | padding:10px; 17 | border:none; 18 | border-radius:10px; 19 | background-color:white; 20 | } 21 | 22 | QMenu::item { 23 | padding:10px; 24 | border:none; 25 | border-radius:6px; 26 | background-color: white; 27 | color: black; 28 | } 29 | 30 | QMenu::item:selected { 31 | background-color:#eaeffd; 32 | color:blue; 33 | } 34 | 35 | QWidget#widget_sidebar { 36 | 37 | border-right: 2px solid #f3f6fe; 38 | } 39 | 40 | QWidget#widget_sidebar QPushButton:checked { 41 | background-color:#f3f6fe; 42 | } 43 | 44 | QWidget#inner_widget{ 45 | border-radius:6px; 46 | background-color:white; 47 | } 48 | QWidget#inner_widget QLabel{ 49 | border-radius:6px; 50 | } 51 | 52 | QWidget#widget_lrc { 53 | border-radius:6px; 54 | } 55 | 56 | QTabWidget#tab_lrc{ 57 | border-radius:6px; 58 | } 59 | 60 | QTabWidget::pane { 61 | bottom:-11px; 62 | top:-11px; 63 | left:-11px; 64 | right:-11px; 65 | } 66 | 67 | QLineEdit { 68 | background-color:#f2f2f4; 69 | font-size:20px; 70 | color: #335eea; 71 | margin-left: 5px; 72 | padding: 10px; 73 | border: none; 74 | border-top-left-radius: 8px; 75 | border-bottom-left-radius: 8px; 76 | } 77 | 78 | QLineEdit#lineEdit_listName{ 79 | border-radius: 8px; 80 | } 81 | 82 | QPushButton { 83 | icon-size: 32px; 84 | border: none; 85 | border-radius: 10px; 86 | } 87 | 88 | QPushButton:hover { 89 | background-color: rgb(228, 237, 242); 90 | } 91 | 92 | QPushButton:selected { 93 | background-color: rgb(228, 237, 242); 94 | } 95 | 96 | QPushButton:disabled { 97 | color:black; 98 | } 99 | 100 | QPushButton#pushButton_dropDown { 101 | padding-top:10px; 102 | margin-left:15px; 103 | margin-top:15px; 104 | icon: url(:/icon/downArow_gray.svg); 105 | } 106 | 107 | QPushButton#pushButton_dropDown:hover { 108 | icon: url(:/icon/downArow_black.svg); 109 | margin-top:15px; 110 | margin-left:15px; 111 | } 112 | 113 | QPushButton#pushButton_search{ 114 | background-color:#f2f2f4; 115 | margin:0px; 116 | margin-left:-5px; 117 | padding: 5px; 118 | border-top-right-radius: 8px; 119 | border-bottom-right-radius: 8px; 120 | } 121 | 122 | QPushButton#pushButton_close:hover { 123 | background-color:red; 124 | } 125 | 126 | QPushButton#pushButton_newSongList { 127 | background-color: #f2f2f4; 128 | font-size: 23px; 129 | icon-size: 20px; 130 | padding: 10px; 131 | } 132 | 133 | QLabel#label_title { 134 | font-size: 30px; 135 | } 136 | 137 | QLabel#label_searchKeywords, 138 | #label_favorite, 139 | #label_local, 140 | #label_recently, 141 | #label_mySonglist, 142 | #label_listName { 143 | font: 26pt "华文楷体"; 144 | } 145 | 146 | QLabel#label_songname, 147 | QLabel#label_lrc_songName{ 148 | font: 16pt "华文楷体"; 149 | } 150 | 151 | QLabel#label_lrc_songName { 152 | color:white; 153 | } 154 | 155 | QLabel#label_songName { 156 | font: 14pt "华文楷体"; 157 | } 158 | 159 | QLabel#label_author, 160 | QLabel#label_lrc_author{ 161 | color:#727272; 162 | margin:0; 163 | font: 10pt "华文楷体"; 164 | } 165 | 166 | QLabel#label_lrc_songName, 167 | QLabel#label_lrc_author 168 | { 169 | color: white; 170 | margin-top:2px; 171 | padding-left:0px; 172 | } 173 | 174 | QLabel#label_lrc_songName { 175 | margin-top:10px; 176 | } 177 | 178 | QLabel#label_albumName { 179 | font: 12pt "华文楷体"; 180 | } 181 | 182 | QLabel#label_duration { 183 | font: 12pt "华文楷体"; 184 | } 185 | 186 | QToolTip { 187 | color:black; 188 | background-color:white; 189 | border: 1px solid white; 190 | border-radius: 2px; 191 | } 192 | 193 | QTabWidget::pane{ 194 | border:none; 195 | } 196 | 197 | QListWidget { 198 | outline:0px; 199 | } 200 | 201 | QListWidget::item { 202 | border: none; 203 | border-radius:10px; 204 | } 205 | 206 | QListWidget::item:hover { 207 | background-color: #ececf4; 208 | } 209 | 210 | QListWidget::item:selected { 211 | color:blue; 212 | background-color:rgba(234, 239, 253, 150); 213 | } 214 | 215 | QListWidget#listWidget_lrc::item { 216 | color:rgba(255, 255, 255, 150); 217 | padding-top: 15px; 218 | padding-bottom:15px; 219 | background-color: transparent; 220 | } 221 | 222 | QListWidget#listWidget_searchResult::item:!pressed { 223 | color:black; 224 | } 225 | 226 | QListWidget#listWidget_lrc { 227 | font-size:40px; 228 | background-color: transparent; 229 | } 230 | 231 | QListWidget#listWidget_songlistWidget { 232 | margin-top: 10px; 233 | } 234 | 235 | QListWidget#listWidget_songlistWidget::item { 236 | font-size:25px; 237 | padding: 10px; 238 | } 239 | 240 | QListWidget#listWidget_songlistWidget::item:hover { 241 | background-color: #f2f2f4; 242 | } 243 | 244 | QListWidget#listWidget_lrc::item:selected { 245 | color:rgba(0, 0, 255, 100); 246 | background-color:rgba(234, 239, 253, 150); 247 | } 248 | 249 | QListWidget#listWidget_lrc::item:hover { 250 | color:rgba(255, 255, 255, 100); 251 | background-color:rgba(245, 245, 247, 150); 252 | } 253 | 254 | QListWidget#searchTips { 255 | border: none; 256 | background-color: white; 257 | font-size:25px; 258 | border-radius: 5px; 259 | } 260 | 261 | QListWidget#searchTips::item { 262 | margin: 5px; 263 | padding: 5px; 264 | } 265 | 266 | QListWidget#listWidget_songList::item:hover { 267 | background-color: transparent; 268 | } 269 | 270 | QListWidget#listWidget_songList::item:selected { 271 | background-color: transparent; 272 | } 273 | 274 | 275 | QScrollBar:vertical { 276 | margin: 5px 0px 5px 0px; 277 | background-color: white; 278 | border: 0px; 279 | width: 8px; 280 | } 281 | 282 | QScrollBar::handle:vertical { 283 | background-color: rgba(153, 153, 153, 0.735); 284 | border-radius: 4px; 285 | width: 8px; 286 | height: 15px; 287 | } 288 | 289 | QScrollBar::handle:vertical:hover { 290 | background-color: #999; 291 | } 292 | 293 | QScrollBar::sub-line:vertical { 294 | height: 0px; 295 | color: white; 296 | } 297 | 298 | QScrollBar::add-line:vertical { 299 | height: 0px; 300 | color: white; 301 | } 302 | 303 | QScrollBar::up-arrow:vertical { 304 | width: 12px; 305 | height: 0px; 306 | color: white; 307 | } 308 | 309 | QScrollBar::down-arrow:vertical { 310 | width: 12px; 311 | height: 0px; 312 | color: white; 313 | } 314 | 315 | QScrollBar::sub-page:vertical, 316 | QScrollBar::add-page:vertical { 317 | background-color: white; 318 | } 319 | 320 | QSlider { 321 | background-color:transparent; 322 | } 323 | QSlider::groove:horizontal { 324 | margin-top:0px; 325 | margin-left:-7px; 326 | margin-right:-7px; 327 | border: 1px solid #bbb; 328 | background: transparent; 329 | height: 3px; 330 | border-radius:1px; 331 | } 332 | 333 | QSlider::sub-page:horizontal { 334 | background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1, stop: 0 #bbf, stop: 1 #55f); 335 | border: 1px solid #777; 336 | height: 2px; 337 | border-radius:1px; 338 | } 339 | QSlider::add-page:horizontal { 340 | background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1, stop: 0 #bbf, stop: 1 #55f); 341 | border: 1px solid #777; 342 | height: 2px; 343 | border-radius:1px; 344 | } 345 | 346 | QSlider::handle:horizontal { 347 | border: 1px solid gray; 348 | background-color:white; 349 | width: 8px; 350 | height:8px; 351 | margin-top: -4px; 352 | margin-bottom: -4px; 353 | border-radius: 4px; 354 | } 355 | 356 | QSlider#horizontalSlider_volume::groove:horizontal { 357 | margin-left:2px; 358 | padding-left:-2px; 359 | } 360 | -------------------------------------------------------------------------------- /switchanimation.cpp: -------------------------------------------------------------------------------- 1 | #include "switchanimation.h" 2 | 3 | 4 | SwitchAnimation::SwitchAnimation(QWidget *parent) 5 | : QWidget{parent} 6 | { 7 | 8 | } 9 | 10 | void SwitchAnimation::slideAnimation(QWidget *parent, QWidget *slideWidget, int direction) 11 | { 12 | // 初始化label,用来存储界面的截图 13 | if (!m_leftLabel) 14 | m_leftLabel = new QLabel(parent); 15 | m_leftLabel->setStyleSheet("background-color: white"); 16 | m_leftLabel->resize(slideWidget->size()); 17 | m_leftLabel->raise(); 18 | 19 | // 设置界面动画 20 | if (!anim) { 21 | anim = new QPropertyAnimation(m_leftLabel, "geometry", this); 22 | connect(anim, &QPropertyAnimation::finished, [=] { 23 | m_leftLabel->hide(); 24 | }); 25 | } 26 | anim->setDuration(animTime); 27 | anim->setEndValue(slideWidget->geometry()); 28 | anim->setEasingCurve(QEasingCurve::OutCubic); 29 | // 确定动画方向 30 | switch (direction) { 31 | case AnimDirection::Forward: 32 | case AnimDirection::Backward: 33 | anim->setStartValue(QRect(slideWidget->width(), 0, slideWidget->width(), slideWidget->height())); 34 | break; 35 | case AnimDirection::Up: 36 | case AnimDirection::Down: 37 | anim->setStartValue(QRect(0, slideWidget->height(), slideWidget->width(), slideWidget->height())); 38 | break; 39 | } 40 | 41 | // 用label存储两个界面的截图 42 | m_leftLabel->setPixmap(slideWidget->grab(slideWidget->rect()).scaled(m_leftLabel->size())); 43 | // m_leftLabel->setPixmap(QPixmap::grabWidget(slideWidget)); 44 | // 显示label 45 | m_leftLabel->show(); 46 | // 提升label 47 | m_leftLabel->raise(); 48 | 49 | switch (direction) { 50 | case AnimDirection::Forward: 51 | case AnimDirection::Up: 52 | anim->setDirection(QAbstractAnimation::Forward); 53 | break; 54 | case AnimDirection::Down: 55 | case AnimDirection::Backward: 56 | anim->setDirection(QAbstractAnimation::Backward); 57 | break; 58 | } 59 | // 播放动画 60 | anim->start(); 61 | } 62 | -------------------------------------------------------------------------------- /switchanimation.h: -------------------------------------------------------------------------------- 1 | #ifndef SWITCHANIMATION_H 2 | #define SWITCHANIMATION_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class SwitchAnimation : public QWidget 9 | { 10 | public: 11 | enum AnimDirection { 12 | Forward, 13 | Backward, 14 | Up, 15 | Down 16 | }; 17 | SwitchAnimation(QWidget *parent); 18 | /* 19 | * @function: 实现两个窗口的平移切换动画 20 | * @param: parent 两个窗口的父窗口,一般是QStackWidget 21 | * @param: leftWidget 左窗口 22 | * @param: rigjtWidget 右窗口 23 | * @param: direction 平移的方向 24 | */ 25 | void slideAnimation(QWidget *parent, QWidget * slideWidget, int direction = AnimDirection::Up); 26 | protected: 27 | int animTime = 400; 28 | private: 29 | // 界面平移切换动画 30 | QLabel *m_leftLabel = nullptr; 31 | QPropertyAnimation *anim = nullptr; 32 | }; 33 | 34 | #endif // SWITCHANIMATION_H 35 | -------------------------------------------------------------------------------- /threaddownloader.cpp: -------------------------------------------------------------------------------- 1 | #include "threaddownloader.h" 2 | #include 3 | 4 | ThreadDownLoader::ThreadDownLoader(QObject *parent) 5 | : QObject{parent} 6 | { 7 | numThreads = 1; 8 | manager = new QNetworkAccessManager(this); 9 | 10 | } 11 | 12 | void ThreadDownLoader::downLoad(QString &url) 13 | { 14 | threadFinshed = 0; 15 | this->url = url; 16 | // 发送请求获取响应头 17 | QNetworkRequest request; 18 | request.setUrl(QUrl(url)); 19 | headerReply = manager->head(request); 20 | connect(headerReply, &QNetworkReply::finished, this, ThreadDownLoader::onHeaderReplyFinished, Qt::UniqueConnection); 21 | } 22 | 23 | void ThreadDownLoader::onHeaderReplyFinished() 24 | { 25 | // 获取响应状态码,200表示正常 26 | int status_code = headerReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 27 | qDebug()<<__FILE__<<__LINE__ << "状态码:" << status_code; 28 | if (headerReply->error() == QNetworkReply::NoError) { 29 | // 获取专辑图片大小 30 | qint64 totalBytes = headerReply->header(QNetworkRequest::ContentLengthHeader).toLongLong(); 31 | qDebug() << __FILE__ << __LINE__ << "totalBytes:" <deleteLater(); 38 | headerReply = nullptr; 39 | } 40 | } 41 | 42 | void ThreadDownLoader::multiDownLoad(QString &url, qint64 totalBytes) 43 | { 44 | int mb = totalBytes /1000000; 45 | if (mb > 9) 46 | numThreads = 3; 47 | else 48 | numThreads = 2; 49 | 50 | // 根据线程数分段,计算出每一段的大小 51 | qint64 partSize = totalBytes / numThreads; 52 | qDebug() << __FILE__ << __LINE__ <<"partSize:"<> parts; 55 | qint64 startByte = 0; 56 | qint64 endByte = -1; 57 | for (int i = 0; i < numThreads; i++) { 58 | startByte = endByte + 1; 59 | endByte = startByte + partSize - 1; 60 | if (i == numThreads - 1) { 61 | endByte = totalBytes - 1; 62 | } 63 | parts.append(qMakePair(startByte, endByte)); 64 | qDebug() << __FILE__ << __LINE__ << "index:"<get(request); 72 | replies.push_back(reply); 73 | connect(reply, &QNetworkReply::finished, this, &ThreadDownLoader::onReplyFinished); 74 | } 75 | } 76 | 77 | void ThreadDownLoader::onReplyFinished() 78 | { 79 | // 等待所有请求完成 80 | threadFinshed++; 81 | if (threadFinshed < numThreads) 82 | return; 83 | // 合并每一段的数据 84 | QByteArray bytes; 85 | for (int i = 0, length = replies.size(); i < length; i++) { 86 | QNetworkReply *reply = replies.at(i); 87 | bytes.append(reply->readAll()); 88 | reply->deleteLater(); 89 | } 90 | // 发送完成信号 91 | emit finished(bytes); 92 | qDebug()<<"MultiDownLoad finished"; 93 | } 94 | 95 | 96 | -------------------------------------------------------------------------------- /threaddownloader.h: -------------------------------------------------------------------------------- 1 | #ifndef THREADDOWNLOADER_H 2 | #define THREADDOWNLOADER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class ThreadDownLoader : public QObject 10 | { 11 | Q_OBJECT 12 | public: 13 | explicit ThreadDownLoader(QObject *parent = nullptr); 14 | // 下载 15 | void downLoad(QString &url); 16 | // 多线程分段下载 17 | void multiDownLoad(QString &url, qint64 totalBytes); 18 | signals: 19 | // 下载完成信号 20 | void finished(QByteArray bytes); 21 | private: 22 | QNetworkAccessManager *manager; // 网络访问管理 23 | QVector replies; // 每一个段的网络应答 24 | QNetworkReply *headerReply; // 网络响应头的应答 25 | int numThreads; // 线程数 26 | int threadFinshed; // 已完成的请求数 27 | QString url; // 专辑图片链接 28 | private slots: 29 | // 响应头返回 30 | void onHeaderReplyFinished(); 31 | // 某一段响应返回 32 | void onReplyFinished(); 33 | private: 34 | 35 | }; 36 | 37 | #endif // THREADDOWNLOADER_H 38 | -------------------------------------------------------------------------------- /更新日志.md: -------------------------------------------------------------------------------- 1 | ## 更新日志 2 | 3 | ### 2023.05.22 4 | - 完善配置信息 5 | - 修复初始化配置信息时读取不到未加载歌单的bug 6 | 7 | ### 2023.05.19 8 | - 修复删除音乐后歌单封面不更新的bug 9 | - 修复可以删除原有歌单的bug 10 | - 修复歌单中没有歌曲打开后无法向其中添加音乐的bug 11 | 12 | ### 2023.05.18 13 | - 歌单封面显示 14 | - 配置文件 15 | - 修复播放最近音乐之后播放其他歌单音乐崩溃 16 | 17 | ### 2023.05.16 18 | - 修复下一首播放链接为空不能继续下一首的bug 19 | 20 | ### 2023.05.11 21 | - 优化图片加载速度 22 | 23 | ### 2023.05.10 24 | - 歌曲切换按钮防抖动 25 | - 文字溢出省略 26 | - 搜索提示列表的展开/隐藏动画 27 | - 修复动画不显示的BUG 28 | --------------------------------------------------------------------------------