> 作者:?舊言~
> 座右銘:松樹千年終是朽,槿花一日自為榮。> 目標:能自我實現簡易版的 QQ 音樂。
> 毒雞湯:有些事情,總是不明白,所以我不會堅持。早安!
> 專欄選自:實戰項目_?舊言~的博客-CSDN博客
> 望小伙伴們點贊👍收藏?加關注喲💕💕
一、項目演示
演示效果
該項目是基于 QT 開發的音樂播放軟件,界面友好,功能豐富,旨在加強同學們對 QT 知識的綜合應用,以及熟悉 QT 項目開發的流程。主要功能如下:
窗口 head 部分:
- 點擊最小化按鈕,窗口最小化
- 點擊最大化按鈕,窗口示反應(即禁止窗口最大化)
- 點擊關閉按鈕,程序退出
- 點擊皮膚按鈕,更換皮膚(該功能暫未支持,uu 可私下擴展,最簡單的方式就是更換窗體的背景顏色)
- 搜索框搜索功能(該功能暫未支持,uu 可私下擴展)
窗口 body 左側:
- 點擊推薦按鈕,窗口右側顯示:推薦Page(暫只有頁面)
- 點擊電臺按鈕,窗口右側顯示:電臺Page(未支持)
- 點擊?樂館按鈕,窗口右側顯示:音樂館Page(未支持)
- 點擊我喜歡按鈕,窗口右側顯示:收藏的音樂Page
- 點擊本地下載按鈕,窗口右側顯示:本地音樂Page
- 點擊最近播放按鈕,窗口右側顯示:最近播放Page
注意:左側按鈕,當光標懸停時會有不同顏?突出顯?,當點擊時會有綠?顯?,并且按鈕的
右側有跳動的豎條。
窗?右側:
- 點擊全部播放按鈕,播放當前頁面列表中所有音樂
- 雙擊列表中某音樂,播放當前選中音樂
- 點擊心支持收藏
- ?持最近播放過音樂記憶
播放控制區:
- 支持seek功能,即拖拽到歌曲指定位置播放
- 支持:隨機、單曲循環、循環播放
- 支持播放上?曲
- 支持播放上一曲
- 支持播放下?曲
- 支持播放和暫停
- 支持音量調節和靜?
- 支持歌曲總時長顯示 + 當前播放時間顯示
- 支持持 LRC 歌詞同步顯示
二、界面開發
2.1、界面簡要分析
界?上控件比較多,歸類之后主要分為兩部分:head 區和 body 區。
head 區域從左往右依次為:圖標、搜索框、更換皮膚按鈕、最小化 & 最大化 & 退出按鈕。
body 區域分為左側種類選擇區域和右側 Page 展示區:
Body左側區域有兩部分組成:在線音樂 和 我的音樂,兩部分內部的控件種類是相同的。
- ① 說明區域,實際為 QLabel
- ② 自定義控件(按鈕的擴展):圖片+文本+動畫
- ③ 同②,?定義控件(按鈕的擴展):圖片+文本+動畫
- ④ 同②,?定義控件(按鈕的擴展):圖片+文本+動畫
Body 右側區域由:Page 區、播放進度、播放控制區三部分構成。
- ① Page區:歌曲信息頁面,點擊 < 或 > 具有輪播圖效果
- ② 播放進度:當前歌曲播放進度說明,支持seek功能,與播放控制區時間、以及LRC歌詞是同步的
- ③ 播放控制區域:顯示歌曲圖片 & 名稱 & 歌手、播放模式 & 下?曲 & 播放暫停 & 上?曲 & ?量調節和靜? & 添加本地?樂、當前播放時間 / 歌曲總時長?& 彈出歌詞窗口按鈕
整個頁面內容可以分為上下兩組:今日為你推薦、你的歌曲補給站。兩組的布局實際是相同的,元素:
- 上方顯示 1 行,內部有4個推薦元素;下方顯示 2 行,每行有 4 個推薦元素
- 左右兩側?個按鈕,點擊后推薦內容會更換下一批,不停點擊會循環推薦
- 當鼠標懸停在推薦元素上時,推薦元素會向上移動,當鼠標離開時,又回到原位置
- 當鼠標懸停在推薦元素上時,同時會出現小手圖標,說明該推薦元素具有點擊功能
我喜歡、本地下載、最近播放類似下圖:
這三個Page中布局、控件都是相同的,只是填充的數據不?樣。每個Page中包含了多個控件,大致如下:
- ① QLabel:類型說明
- ② QLabel:圖?顯?
- ③ QButton:播放全部按鈕
- ④ ?組QLabel說明:?樂、歌?、專輯
- ⑤ QListWidget:播放列表
歌詞頁面:解析當前正在播放音樂的歌詞,同步顯示在界面上。
顯示內容分為:歌曲信息、歌詞部分、左上方收起隱藏按鈕。
- 歌曲信息由歌曲名稱(QLabel)和歌手名稱(QLabel)構成
- 歌詞部分展示當前在唱歌詞(QLabel)和在唱部分前三行和后三行歌詞(QLabel)展示,當前播放
- 歌詞突出顯示
- 點擊收起按鈕后,該頁面會以動畫的方式收起
說明:
- 當歌曲有LRC歌詞時,播放時歌詞會隨播放時間自動調整;歌曲沒有LRC歌詞時,歌詞部分顯示空字符。
- 以上對本項目的界面進行了簡單的說明,大家先有個初步了解,接下來利用QT Designer完成界?的布局。
2.2、界面開發
2.2.1、創建工程
創建一個基于 QWidget 的工程,選中生成 form 選項,將來界面部分主要使用 QDesigner 來設計:
2.2.2、主界面布局設置
基于Widget局部:
QT系統提供4種布局管理器:
- QHBoxLayout:水平布局
- QVBoxLayout:垂直布局
- QGridLayout:柵格布局
- QFormLayout:表單布局
為什么采用 widget 來設計:
由于?個 widget 中只能包含上述布局管理器中的?種,所以直接使用布局管理器來布局不是很靈活;而一個 widget 中可以包含多個 widget ,在 widget 中的控件可以進行水平、垂直、柵格、表單等布局操作,非常靈活。
窗口主框架設計:
主窗口的布局:
① 選中 QQMusic ,在彈出的屬性中找到 geometry 屬性,將窗口寬度修改為:1040,高度修改為700
② 從控件區拖拽?個 Widget 到窗口區域,objectName 修改為:background,選中 QQMusic ,然后點擊垂直布局,background 就填充滿了整個窗口。
為了看到效果,選中 backroound 控件,然后右鍵單擊,彈出菜單中選擇改變樣式表,內部添加:
background-color:gray;
整個窗口由 head 和 body 上下兩部分組成:
- 直接拖兩個 Widget 放到設計區,雙擊將名字修改為 head 和 body
- 修改背景顏色方便查看效果,head 背景色修改為 green,body 背景色修改為 pink
background-color:green;
background-color:pink;
head 在上,body 在下,然后選中 background 對象,點擊垂直布局:
head 和 body 平分了整個background,并且 head 和 body 的 margin 有間隔。再次選中background 對象,右側屬性部分下滑找到 Layout ,將 Margni 和 Space 修改為 0:
但是 head 占區域過大,選中 head 對象,將 head 的 minimumSize 和 maxmumSize 屬性的高度都調整為 80 ,這樣head的大小就固定了:
head 內部設計:
head 內部由兩部分構成,headLeft 區域顯?圖標,headRight 區域為搜索框和功能按鈕區域。拖兩個 widget 到 head 中,將 objectName 修改為 headLeft 和 headRight ,背景顏色修改為:
然后選中 head 對象,點擊水平布局(垂直布局左側就是水平布局),就會呈現如下布局:
繼續選中 head 對象,下滑找到 Layout 屬性,將 Margin 和 Spacing 全部設置為 0 ,選中 headLeft 對象,將 minimumSize 和 maximumSize 的寬度修改為 200 ,就能看到 head 的初步效果:
headLeft:
拖?個 QLabel 控件放置 headLeft 內,將 QLabel 的 objectName 修改為 logo ,text 屬性修改為空;然后選中 headLeft ,點擊水平布局,此時 QLabel 就會填充滿 headLeft 。同樣需要選中headLeft,下滑找到 Layout 屬性,將 Margin 和 spacing 全部設置為0
headRight:
headRight 內部也是由兩部分構成:搜索框和按鈕區域拖拽兩個 widget 到 headRight ,修改objectName 為 SearchBox 和 SettingBox ,將 SearchBox 的 minimumSize 和 maximumSize 的寬度修改為 300,背景顏色分別修改為:
選中 headRight ,然后點擊水平布局,并將 headRight 的 Margin 和 Spacing 修改為 0 ,就能看到下面的效果:
searchBox:
拖?個 QLineEdit 進去,然后選中 searchBox 點擊水平布局
settingBox:
拖拽?個按鈕到 SettingBox ,按鈕的 minimumSize 和 maximumSize 的寬度和高度都修改為 30,然后鼠標選中,按著 ctrl鍵+鼠標 拖拽,復制 3 個出來擺放好,依次將四個按鈕 objectName 從左往右修改為:skin、max、min、quit,并將按鈕的 text 屬性也修改為空,將來設置圖片。在控件區域找到 Spacers ,找到 Horizontal Spacer 控件,拖拽到 SettingBox 區域。
選中 SettingBox ,點擊水平布局,并將 SettingBox 的 Margin 和 Spacing 修改為 0
Body部分布局:
整個body部分是由bodyLeft和bodyRight兩部分組成:
① 拖兩個 Widget 到 Body 中,將 objectName 修改為 bodyLeft 和 bodyRight
② 將 bodyLeft 顏色修改為:
③ 選中 body,點擊水平布局,將 bodyLeft 的 minimumSize 和 maxmumSize 的寬度修改為 200
④ 選中 Body,將 body 的 Margin 和 Spacing 修改為 0
bodyLeft 內部布局:
- 拖拽?個 Widget 到 bodyLeft ,將 objectName 修改為 leftBox ,背景顏?修改為:background-color:pink.
- 拖拽 Vertical Spacer 到 bodyLeft.
- 選中 leftBox ,將 minmumSize 和 maxmumSize 的高度修改為 400.
- 選中 bodyLeft ,點擊垂直布局,并將 bodyLeft 的 Margin 和 Spacing 修改為 0.
leftBox 內部布局:
leftBox內部包含:在線音樂 和 我的音樂?兩部分。
① 拖拽兩個 Widget 到 leftBox 中,將 objectName 依次修改為:onlineMusic 和 myMusic
② 顏色分別修改為:
③ 選中 leftBox ,點擊垂直布局,然后將 Margin 和 Spacing 設置為 0
④ onlineMusic 和 myMusic 內部的元素都是相同的,由?個 QLabel 和三個 Widget 構成,后期Widget 會替換為自定義按鈕,此處先用 Widget 占位。因此分別向 onlineMusic 和 myMusic 內部拖拽?個 QLabel 和三個 QWidget ,并選中 onlineMusic 和 myMusic 點擊垂直布局,然后將Margin 和 Spacing 設置為 0
bodyRight布局:
bodyRight 由層疊窗口、進度滑竿、播放控制區三部分組成:
① 拖拽層疊窗口控件 Stacked Widget ,就在 Widget 控件上方到 bodyRight 中
② 拖拽 Widget 到 bodyRight ,將 objectName 修改為 processBar,將 minimumSize 和maximumSize 的高度修改為 30 ,背景顏色修改為綠色。
③ 拖拽 Widget 到 bodyRight ,將 objectName 修改為 controlBox ,將 minmumSize 高度修改為 60
④ 選中 bodyRight ,點擊垂直布局,然后將 bodyRight 的 Margin 和 Spacing 修改為 0
⑤ 為了能看到效果,將 processBar 顏色修改為:background-color:pink.
stackedWidget 內部增加頁面:
stackedWidget 默認會提供兩個頁面,還需添加四個頁面。
在對象區域選中 stackedWidget 控件,然后右鍵單擊彈出菜單中選擇添加頁:
以類似的方式添加添加 4 個頁面,并修改每個頁面的 objectName 如下:
選中 stackedWidget ,然后右鍵單擊,彈出菜單中選擇:改變頁順序,在彈出的窗口中就能看到每個頁面的索引:
六個頁面中,recPage 頁面需要實現,musicPage、radioPage 暫未支持,同學們可自行擴展likePage、localPage、recentPage 三個頁面都是雷同的,將來自定義即可。
ControlBox內部布局:
該區域內部由三部分組成:歌曲信息部分、播放控制部分、時間顯示
① 拖拽三個 Widget 到 ControlBox 中,將 ObjectName 依次修改為 play_1、play_2、play_3 顏色依次修改為:
② 選中 ControlBox ,點擊水平布局,將 ControlBox 的 Margin 和 Spacing 修改為 0
play1 內部:
拖拽 3 個 QLabel ,放置歌曲圖片、歌手名和歌曲名字,調整好位置,將 QLabel 的 objectName 修改為:musicCover、musicName、musicSinger然后選中 play1 ,點擊柵格布局
play2 內部:
QWidget::setWindowFlag(...): 設置窗?格式,?如創建?邊框的窗?
從左到右依次擺放 6 個按鈕,按鈕的 minimumSize 和 maxmumSize 均修改為 30 * 30 ,將objectName 從左往右依次修改為:playMode、playUp、Play、playDown、volume、addLocal;然后選中 play2 ,點擊水平布局,并將 play_2 的 Margin 和 Spacing 修改為 0.
play3 內部:
拖四個 QLabel 和一個按鈕,調整大小位置,從左往右 QLabel 的 objectName 依次修改為:labelNull、currentTime、line、totalTime,按鈕的 objectName 修改為 lrcWord ,按鈕的maxmumSize 的寬度和高度修改為 30*30 ;選中 play3 ,點擊水平布局,并將 play2 的 Margin 和Spacing 修改為 0.
2.3、界面美化
2.3.1 、主窗口設定
設置窗口:
仔細觀察發現主窗口是沒有標題欄,因此在窗口創建前,就需要設置下窗口的格式:
QWidget::setWindowFlag(...): 設置窗?格式,?如創建?邊框的窗?
由于窗口中控件比較多,這些控件將來都需要初始化,如果將所有代碼放在 QQMusic 的構造函數中實現,將來會造成構造函數非常臃腫,因此在 QQMusic 類中添加 initUI() 方法來完成界面初始化工作:
// QQMusic.h ?件中添加:
void initUI();
// 添加完成后,光標放在函數名字上按 alt + Enter 組合鍵完成?法定義
// QQMusic.cpp 頭?件中完成定義void QQMusic::initUI()
{// 設置?邊框窗?,即窗?將來?標題欄setWindowFlag(Qt::WindowType::FramelessWindowHint);
}
運行后,發現有以下兩個問題:
- 窗口無標題欄,找不到關閉按鈕,導致窗口無法關閉
- 窗口無法拖拽
關閉窗口,可以先將光標放在任務欄中當前應用程序圖標上,彈出的框中選擇關閉,后序會實現關閉功能:
主界面無法拖動,此時只需要處理下鼠標單擊 (mousePressEvent) 和 鼠標移動(mouseMoveEvent) 事件即可。鼠標左鍵按下時,記錄下窗口左上角和鼠標的相對位置鼠標移動時,會產生新的位置,保持鼠標和窗口左上角相對位置不變,通過 move 修改窗口的左上叫坐標即可:
/
// QQMusic.h中添加
protected:
// 重寫QWidget類的?標單擊和?標滾輪事件
void mousePressEvent(QMouseEvent *event)override;
void mouseMoveEvent(QMouseEvent* event)override;
// 記錄光標相對于窗?標題欄的相對距離
QPoint dragPosition;
/
// QQMusic.cpp中添加void QQMusic::mousePressEvent(QMouseEvent *event)
{// 攔截?標左鍵單擊事件if(event->button() == Qt::LeftButton){// event->globalPos():?標按下事件發?時,光標相對于屏幕左上?位置// frameGeometry().topLeft(): ?標按下事件發?時,窗?左上?位置// geometry(): 不包括邊框及頂部標題區的范圍// frameGeometry(): 包括邊框及頂部標題區的范圍// event->globalPos() - frameGeometry().topLeft() 即為:// ?標按下時,窗?左上?和光標之間的距離差// 想要窗??標按下時窗?移動,只需要在mouseMoveEvent中,讓光標和窗?左上?保持相同的位置差// 獲取?標相對于屏幕左上?的全局坐標dragPosition = event->globalPos() - frameGeometry().topLeft();return;}QWidget::mousePressEvent(event);
}void QQMusic::mouseMoveEvent(QMouseEvent *event)
{if(event->buttons() == Qt::LeftButton){// 根據?標移動更新窗?位置move(event->globalPos() - dragPosition);return;}QWidget::mouseMoveEvent(event);
}
給窗口添加陰影需要用到 QGraphicsDropShadowEffect 類,具體步驟如下:
- 創建 QGraphicsDropShadowEffect 類對象
- 設置陰影的屬性。比如:設置陰影的偏移、顏色、圓角等
- 陰影設置到具體對象上
在 initUI() 函數中添加如下代碼:
// 設置窗?背景透明
this->setAttribute(Qt::WA_TranslucentBackground);// 給窗?設置陰影效果
QGraphicsDropShadowEffect* shadowEffect = new
QGraphicsDropShadowEffect(this);
shadowEffect->setOffset(0,0);// 設置陰影偏移
shadowEffect->setColor("#000000");// 設置陰影顏?:??
shadowEffect->setBlurRadius(10);// 設置陰影的模糊半徑
this->setGraphicsEffect(shadowEffect);
2.3.2、添加圖片資源
添加一個 qrc 文件,將圖片資源拷貝到工程目錄下,并添加到工程中:
將之前布局時所有按鈕的背景全色全部清除掉,按照下面的風格重新設定。
2.3.3、head 處理
顏色查看器:
三、自定義控件
3.1、BtForm
3.1.1、BtForm 界面設計
添加?個新設計界面,命名為BtForm:
該控件實際由:
圖片、文字、動畫三部分組成。圖片和文字分別用QLabel展示,動畫部分內部實際為 4 個QLabel 。
① 將 BtForm 的 geometry 的寬度和高度修改為 200*35。
② 拖?個 Widget 到 btForm 中,objectName 修改為 btStyle,將 btForm 的 margin 和 Spacing 設置為 0.
③ 拖 2 個 QLable 和 1 個 Widget 到 btStyle 中,并將 objectName 依次修改為 btIcon、btText、lineBox
- btIcon 的 minimumSize 和 maximumSize 的寬度設置為 30 (為了看到效果可將顏色設置為red)
- btText 的 minimumSize 和 maximumSize 的寬度設置為 90 (為了看到效果可將顏色設置為green)
- lineBox的 minimumSize 和 maximumSize 的寬度設置為 30
- 然后選中 btStyle,并將其 margin 和 Spacing 設置為 0
④ 然后往 lineBox 內部拖 4 個 QLabel,objectName 依次修改為 line1、line2、line3、line4,minimumSize 和 maximumSize 的寬度均設置為 2
將 bodyLeft 內部 onlineMusic 和 MyMusic 中的 QWidget 全部提升為 BtForm。具體操作:
選中要提升的控件,比如:Rec,在彈出的菜單中選擇提升為,會出現?個新窗口(如下右側圖),在提升的類名稱中輸入要提升為的類型 BtForm,然后點擊添加,最后選中 btform.h 點擊提升,便可以將 Rec 由 QWidget 提升為自定義的 BtForm 類型。
3.1.2、BtForm 類中實現
設置按鈕上的圖片和文字信息,以及該按鈕關聯的 page 頁面:
// btform.h 新增
// 按鈕id:該按鈕對應的page?
int id = 0;
// 設置圖標 ?字 id
void seticon(QString btIcon,QString content,int mid);// btform.cpp新增
void btFrom::seticon(QString btIcon,QString btText,int mid)
{// 設置?定義按鈕的圖?、?字、以及idui->btIcon->setPixmap(QPixmap(btIcon));ui->btText->setText(btText);this->id = mid;
}
在 QQMusic.cpp 的 initUI() 函數中新增:
void Widget::initUi()
{// ...// 設置BodyLeft中6個btForm的信息ui->rec->seticon(":/images/rec.png", "推薦", 1);ui->music->seticon(":/images/music.png", "?樂館", 2);ui->audio->seticon(":/images/radio.png", "電臺", 3);ui->like->seticon(":/images/like.png", "我喜歡", 4);ui->local->seticon(":/images/local.png", "本地下載", 5);ui->recent->seticon(":/images/recent.png", "最近播放", 6);
}
按鈕響應:重寫鼠標 mousePressEvent,當按鈕按下時
①:按鈕顏色發生變化
②:給 QQMusic 類發送 click 信號
// btform.h 新增
protected:// ?標點擊事件virtual void mousePressEvent(QMouseEvent *event);// btform.cpp新增
void btFrom::mousePressEvent(QMouseEvent *event)
{// 告訴編譯器不要觸發警告(void)event;// ?標點擊之后,背景變為綠?,?字變為??ui->btStyle->setStyleSheet("#btStyle{ background:rgb(30,206,154);}*{color:#F6F6F6;}");// 發送?標點擊信號emit click(this->id);
}
③ QQMusic 類處理該信號,內部:實現窗口切換,并清除上次按鈕點擊留下的樣式,因 QQMuisc 中需要新增:
// qqmusic.h 新增// btForm點擊槽函數
void onBtFormClick(int id);// qqmusic.cpp 新增
void QQMusic::connectSignalAndSlot()
{// ...// ?定義的btFrom按鈕點擊信號,當btForm點擊后,設置對應的堆疊窗?connect(ui->rec, &btFrom::click, this, &QQMusic::onBtFormClick);connect(ui->musics, &btFrom::click, this, &QQMusic::onBtFormClick);connect(ui->audio, &btFrom::click, this, &QQMusic::onBtFormClick);connect(ui->like, &btFrom::click, this, &QQMusic::onBtFormClick);connect(ui->local, &btFrom::click, this, &QQMusic::onBtFormClick);connect(ui->recent, &btFrom::click, this, &QQMusic::onBtFormClick);
}void Widget::onBtFormClick(int id)
{// 1.獲取當前??所有btFrom按鈕類型的對象QList<BtForm*> buttonList = this->findChildren<BtForm*>();// 2.遍歷所有對象, 如果不是當前id的按鈕,則把之前設置的背景顏?清除掉foreach (BtForm* btitem, buttonList){if (id != btitem->getId()){btitem->clearBg();}}// 3.設置當前棧空間顯???ui->stackedWidget->setCurrentIndex(id - 1);
}
④ BtForm類中新增:
?
// btform.h 新增
public:// 清除上?次按鈕點擊留下的樣式void clearBg();// 獲取idint getId();// btform.cpp 新增:
void BtForm::clearBg()
{// 清除上?個按鈕點擊的背景效果,恢復之前的樣式ui->btStyle->setStyleSheet("#btStyle:hover{ background:#D8D8D8;} ");
}int BtForm::getId()
{return id;
}
為了能看到 Page 切換的效果,可以在 stackedWidget 的每個 page 上放?個 QLabel 說明。
BtFrom上的動畫效果:
Qt 中 QPropertyAnimation 類可以提供簡單的動畫效果,允許對 QObject 獲取派生類的可讀寫屬性進行動畫處理,創建平滑、連續的動畫效果,比如控件的位置、大小、顏色等屬性變化,使用時需包含<QPropertyAnimation>。
lineBox 中的 line1、line2、line3、line4 添加動畫效果,BtForm 類中增加如下代碼:
// btform.h 新增:// linebox動畫起伏效果QPropertyAnimation *animationLine1;QPropertyAnimation *animationLine2;QPropertyAnimation *animationLine3;QPropertyAnimation *animationLine4;// btform.cpp的構造函數中新增:
BtForm::BtForm(QWidget *parent) :QWidget(parent),ui(new Ui::BtForm)
{ui->setupUi(this);// 設置line1的動畫效果line1Animal = new QPropertyAnimation(ui->line1, "geometry", this);line1Animal->setDuration(1500);line1Animal->setKeyValueAt(0, QRect(0, 15, 2, 0));line1Animal->setKeyValueAt(0.5, QRect(0, 0, 2, 15));line1Animal->setKeyValueAt(1, QRect(0, 15, 2, 0));line1Animal->setLoopCount(-1);line1Animal->start();// 設置line2的動畫效果line2Animal = new QPropertyAnimation(ui->line2, "geometry", this);line2Animal->setDuration(1600);line2Animal->setKeyValueAt(0, QRect(7, 15, 2, 0));line2Animal->setKeyValueAt(0.5, QRect(7, 0, 2, 15));line2Animal->setKeyValueAt(1, QRect(7, 15, 2, 0));line2Animal->setLoopCount(-1);line2Animal->start();// 設置line3的動畫效果line3Animal = new QPropertyAnimation(ui->line3, "geometry", this);line3Animal->setDuration(1700);line3Animal->setKeyValueAt(0, QRect(14, 15, 2, 0));line3Animal->setKeyValueAt(0.5, QRect(14, 0, 2, 15));line3Animal->setKeyValueAt(1, QRect(14, 15, 2, 0));line3Animal->setLoopCount(-1);line3Animal->start();// 設置line4的動畫效果line4Animal = new QPropertyAnimation(ui->line4, "geometry", this);line4Animal->setDuration(1800);line4Animal->setKeyValueAt(0, QRect(21, 15, 2, 0));line4Animal->setKeyValueAt(0.5, QRect(21, 0, 2, 15));line4Animal->setKeyValueAt(1, QRect(21, 15, 2, 0));line4Animal->setLoopCount(-1);line4Animal->start();
}
關于動畫顯示:
動畫并不是所有頁面都顯示,只有當前選中的頁面顯示,所以默認情況下,動畫隱藏。默認情況下設置 addlocal 顯示。
// btform.h 新增:
// 顯?動畫效果
void showAnimal();// btform.cpp的中新增:
void btFrom::showAnimal()
{// 顯?linebox, 設置顏?為綠?ui->linebox->show();
}// QQMusic的initUI中設置默認選中
void QQMusic::initUi()
{// ...// 本地下載BtForm動畫默認顯?ui->local->showAnimal();ui->stackedWidget->setCurrentIndex(4);
}
3.2、推薦頁面
3.2.1、推薦頁面分析
仔細觀察推薦頁面,對其進行拆解發現,推薦頁面由五部分構成:
① "推薦"文本提示,即 QLabel
② "今日為你推薦"文本提示,即 QLabel
③ 具體推薦的歌曲內容,點擊左右兩側翻頁按鈕,具有輪番圖效果,將光標放到圖上,有圖片上移動畫
④ "你的歌曲補給站"文本提示,即 QLabel
⑤ 具體顯示音樂,和③實際是?樣的,不同的是③中音樂只有?行,⑤中的音樂有兩行因為頁面中元素較多,直接擺到?個頁面太擁擠,從右側的滾動條可以看出,整個頁面中的元素都放置在 QScrollArea 中。仔細分析③發現,里面包含了:
3.2.2、推薦頁布局
在 stackedWidget 中選中推薦頁面,objectName 為 recPage 的頁面,刪掉之前添加的 QLabel 推薦提示:
① 拖拽一個 QScrollArea 到 recPage 中,geometry 的寬度和高度修改為 820 和 500,
② 拖拽一個 QLable,objectName 修改為 recText,顯示內容修改為推薦,minimumSize 和maximumSize 的高度均修改為 50,Font 大小修改為 24
③ 再拖拽一個 QLable 和 Widget ,QLable 的 objectName 修改為 recMusictext,內容修改為"今日為你推薦",minimumSize 和maximumSize 的高度均修改為 30,Font 大小修改為 18;Widget 的 objectName 修改為 recMusicBox
④ 再拖拽一個 QLabel 和 Widget ,QLabel 的 objectName 修改為 supplyMusicText ,內容修改為"你的音樂補給站", minimumSize 和 maximumSize 的高度均修改為 30,Font 大小修改為18;Widget 的 objectName 修改為 supplyMusicBox。
⑤ 最后選中 QScrollArea,點擊垂直布局。
3.2.3、自定義 recBox
RecBox 界面布局:
① 新添加設計師界面,命名為 RecBox。geometry 的寬高修改為:685*440。
② 添加三個Widget,objectName 依次修改為 leftPage、musicContent、rightPage;leftPage 和 rightPage 的 minimumSize 和 maximumSize 修改寬為 30,然后選中 RecBox 點擊水平布局。將RecBox 的 margin 和 Spacing 修改為 0
③ 在 upPage 和 downPage 中各拖?個按鈕,upPage 中按鈕 objectName 修改為btUpminimumSize的高度修改為220;downPage 中按鈕 objectName 修改為 btDownminimumSize 的高度修改為 220 ;然后選中 upPage 和 downPage 點擊水平布局。將upPagedownPage 和的 margin 和 Spacing 修改為 0。
④ 在 musicContent 中拖兩個 Widget,objectName 依次修改為 recListUp 和 recListDown,然后選中 musicContent 點擊垂直布局,將 musicContent 的 margin 和 Spacing 修改為 0。(為了看清楚效果可臨時將 recListUp 背景色設置為:background-color:green; 將 recListDown 背景色設置為:background-color:red;)
⑤ 在 recListUp 和 recListDown 中分別拖兩個水平布局器,依次命名為 recListUpHLayout 和
recListDownHLayout,選中 recListUp 和 recListDown 點擊水平布局,將 margin 和 Spacing 修改為 0 按鈕添加如下 QSS 美化:
3.2.4、自定義 recBoxItem
RecBoxItem 界面布局:添加?個 Designer 界面,命名為 RecBoxItem,geometry 的寬和高設置為:150 * 200。
① 拖拽?個 Widget 到 RecBoxItem 中,objectName 修改為 musicImageBox,minimumSize 和maximumSize 的高度均修改為150;
② 拖拽?個 QLabel 到 Widget 中,objectName 修改為 recBoxItemText ,文本設置為"薦-001",QLabel 的 alignment 屬性設置為水平、垂直居中。
③ 拖拽?個 QLabel 到 musicImageBox 中,objectName 修改為 recMusicImage, geometry 設置為:[(0, 0), 150*150]
④ 拖拽一個 QPushButton 到 musicImageBox 中,objectName 修改為 recMusicBtn ,刪除掉文本內容。在屬性中找到 cursor,點擊選擇小手圖標
RecBoxItem 測試:
// recBox.cpp構造函數中添加如下代碼
RecBoxItem* item = new RecBoxItem();
ui->recListUpHLayout->addWidget(item);
RecBoxItem 類中添加動畫效果:
- 在RecBoxItem類中攔截鼠標進入和離開事件,在進入時讓圖片上移,在離開時讓圖片下移回到原位
- 該類中還需要添加設置推薦文本和圖片的方法,將來需要在外部來設置每個 RecBoxItem 的文本和圖片
3.2.5、RecBox 添加 RecBoxItem
圖片路徑和推薦文本準備:
每個 RecBoxItem 都有對應的圖片和推薦文本,在往 RecBox 中添加 RecBoxItem 前需要先將圖片路徑和對應文本準備好。由于圖片和文本具有對應關系,可以以鍵值對方式來進進組織,以下實現的時采用 QT 內置的 QJsonObject 對象管理圖片路徑和文本內容。
QJsonObject類:
頭?件: <QJsonObject>
// 功能: 插?<key, value>鍵值對,如果key已經存在,則?value更新與key對應的value
// 返回值:返回指向新插?項的鍵值對
QJsonObject::iterator insert(const QString &key, const QJsonValue &value);
// 功能:獲取與key對應的value
// 返回值:返回的value?QJsonValue對象組織
QJsonValue QJsonObject::value(const QString &key) constQJsonArray類:
作?:管理的是QJsonValue對象
頭?件:<QJsonArray>
該類重載了[]運算符,可以通過下標?式獲取管理的QJsonValue對象
QJsonValue operator[](int i) const
QJsonValueRef operator[](int i)
// 往QJsonArray中插??個QJsonValue對象
void append(const QJsonValue &value)QJsonValue類
// 單參構造?法,將QJsonObject對象轉換為QJsonValue對象
QJsonValue(const QJsonObject &o)
// 將內部管理的數據轉化成QJsonObject返回
QJsonObject toObject() const
// 將內部管理的數據轉化成QString返回
QString toString() const
recBox 中添加元素:
由于 recPage 頁面中有兩個 RecBox 控件,上面的 RecBox 為一行四列,下方的 RecBox 為 2 行四列,因此在 RecBox 類中增加以下成員變量:
// RecBox.h 新增
#include <QJsonArray>
public:void initRecBoxUi(QJsonArray data, int row);private:int row;// 記錄當前RecBox實際總?數int col;// 記錄當前RecBox實際每?有?個元素QJsonArray imageList; // 保存界?上的圖?, ??實際為key、value鍵值對
3.2.6、RecBox 中 btUp 和 btDown 按鈕 clicked 處理
添加槽函數:
選中 recbox.ui 文件,分別選中 btUp 和 btDown,右鍵單擊彈出菜單選擇轉到槽,選中 clicked 確定,btUp 和 btDown 的槽函數就添加好了。
void RecBox::on_btUp_clicked()
{
// 點擊btUp按鈕,顯?前4張圖?,如果已經是第?張圖?,循環從后往前顯?
}
void RecBox::on_btDown_clicked()
{
// 點擊btUp按鈕,顯?前8張圖?,如果已經是第?張圖?,循環從后往前顯?
}
imageList 中圖片分組:
假設 imageList 中 有 24 組圖片路徑和推薦文本信息,如果將信息分組:
- 如果是 recMusicBox ,將元素按照 co l分組,即每 4 個元素為?組,可分為 6 組;如果是supplyMuscBox,將元素按照 col 分組,即每 8 個元素為一組,可分為 3 組。
- RecBox 類中添加 currentIndex 和 count 整形成員變量,currentIndex 記錄當前顯示組,count 記錄總的信息組數。當點擊 btUp 時,currentIndex--,顯示前一組,如果 currentIndex小于 0 時,將其設置為 count-1;當點擊 btDown 按鈕時,currentIndex++ 顯示下?組,當currentIndex為count 時,將 count 設置為 0.
元素重復分析:
每次 btUp 和 btDown 點擊后,應該顯示前一組和后一組圖片,由于之前 recListUpHLayout 和
recListDownHLayout中 已經有元素了,因此需要先將之前的元素刪除掉。
void RecBox::createRecBoxItem()
{// 溢出掉之前舊元素QList<RecBoxItem*> recUpList = ui->recListUp->findChildren<RecBoxItem*>();for(auto e : recUpList){ui->recListUpHLayout->removeWidget(e);delete e;}
QList<RecBoxItem*> recDownList = ui->recListDown->findChildren<RecBoxItem*>
();for(auto e : recDownList){ui->recListDownHLayout->removeWidget(e);delete e;}// 創建RecBoxItem對象,往RecBox中添加// ...
}
按照分組計算 imageList 中元素偏移:
程序啟動時圖片隨機顯示:
仔細觀察發現,每次程序啟動時,顯示的圖片都是相同的,這是因為 random_shuffle 在隨機打亂元素時,需要設置隨機數種子,否則默認使用的種子是相同的,就導致每次打亂的結果都是相同的,所以每次程序啟動時 RecBox 中顯示的內容都是相同的,因此在 randomPiction() 調用之前需要設置隨機數種子。
// QQMusic類的initUi函數中新增
void QQMusic::initUi()
{// ...// 本地下載BtForm動畫默認顯?ui->local->showAnimal();ui->stackedWidget->setCurrentIndex(4);// 設置RecBox圖?、?數srand(time(NULL));ui->recMusicBox->initRecBoxUi(randomPiction(), 1);ui->supplyMuscBox->initRecBoxUi(randomPiction(), 2);
}
3.3、自定義 CommonPage
3.3.1、CommonPage 頁面分析
我的音樂下的:
我喜歡、本地下載、最近播放三個按鈕表面上看對應三個 Page 頁面,分析之后發現,這三個Page 頁面實際是雷同的,因此只需要定義?個頁面 CommonPage ,將 stackedWidget 中這三個頁面的類型提升為 CommonPage 即可。
上圖為本地音樂的 Page 頁面,對頁面拆解后,發現該頁面可以分四部分:
① 頁面說明,比如:本地音樂,該部分實際就是 QLabel 的提示說明;
② 正在播放音樂圖片和播放全部按鈕;
③ 音樂列表中每個部分的文本提示,實際就是三個 QLabel
④ 本頁面對應的音樂列表,即 QListWidget 。
3.3.2、CommonPage 頁面布局
新增加一個設計界面,objectName 修改為 CommonPage,geometry 的寬高修改為 800*500:
① 拖拽?個QLabel、兩個 Widget 和?個 List View 控件到 CommonPage 中,objectName 從上往下依次修改為 pageTittle 、musicPlayBox、listLabelBox、pageMusicList,然后選中CommonPage 點擊垂直布局:
- 將 CommonPage 的 margin 和 Spacing 修改為 0。
- pageTittle 的 minimumSize 和 maximumSize 的高度修改為 30。
- musicPlayBox 的 minimumSize 和 maximumSize 的高度修改為 150。
- listLabelBox 的 minimumSize 和 maximumSize 的高度修改為 40。
② 將 pageTittle 的文本內容修改為"本地音樂"
③ musicPlayBox 中拖拽一個 QLabel,objectName 修改為 musicImageLabel,minimumSize和 maximumSize 的寬度修改為 150
④ listLabelBox 中拖拽三個 QLabel,內容依次修改為:歌曲名稱、歌手名稱、專輯名稱
⑤ 選中 List View,右鍵單擊彈出菜單中選擇"變形為",選擇 QListWidget
3.3.3、CommonPage 界面設置和顯示
CommonPage 頁面是我喜歡、本地下載、最近播放三個界面的共同類型,因此該類需要提供設置:pageTittle 和 musicImageLabel 的公共方法,將來在程序啟動時完成三個界面信息的設置,因此CommonPage 類需要添加一個 public 的 setCommonPageUI 函數
// commonpage.h 中新增
public:void setCommonPageUI(const QString &title, const QString &image);// commonpage.cpp 中新增
void CommonPage::setCommonPageUI(const QString &title, const QString &image)
{// 設置標題ui->pageTittle->setText(title);// 設置封?欄ui->musicImageLabel->setPixmap(QPixmap(image));ui->musicImageLabel->setScaledContents(true);
}
界面設置的函數需要在程序啟動時就完成好配置,即需要在 QQMusic 的 initUi() 函數中調用完成設置:
void Widget::initUi()
{....// 設置我喜歡、本地?樂、最近播放??ui->likePage->setCommonPageUI("我喜歡", ":/images/ilikebg.png");ui->localPage->setCommonPageUI("本地?樂", ":/images/localbg.png");ui->recentPage->setCommonPageUI("最近播放", ":/images/recentbg.png");
}
3.4、自定義 ListItemBox
3.4.1、ListItemBox 頁面分析
CommonPage 頁面創建好之后,等音樂加載到程序之后,就可以將音樂信息往 CommonPage 的 pageMusicList 中顯示了。
上圖每行都是 QListWidget 中的一個元素,每個元素中包含多個控件:
① 收藏圖標,即QLabel
② 歌曲名稱,即QLabel
③ VIP 和 SQ,VIP 即收費會員專享,SQ 為無損音樂,也是兩個 QLabel
④ 歌手名稱,即 QLabel
⑤ 音樂專輯名稱,即 QLabel
此處,需要將上述所有 QLabel 組合在?起,作為一個獨立的控件,添加到 QListWidget 中,因此該控件也需要自定義。
3.4.2、ListItemBox 頁面布局?
添加一個設計師界面,objectName 為 ListItemBox,geometry 的寬度和高度修改為 800*45:
3.4.3、ListItemBox 顯示測試
ListItemBox 將來要添加到 CommonPage 頁面中的 QListWidget 中,因此在 CommonPage 類的初始化方法中添加如下代碼:
#include "listitembox.h"void CommonPage::setCommonPageUI(const QString &title, const QString &image)
{// 設置標題ui->pageTittle->setText(title);// 設置封?欄ui->musicImageLabel->setPixmap(QPixmap(image));ui->musicImageLabel->setScaledContents(true);// 測試ListItemBox* listItemBox = new ListItemBox(this);QListWidgetItem* listWidgetItem = new QListWidgetItem(ui->pageMusicList);listWidgetItem->setSizeHint(QSize(ui->pageMusicList->width(), 45));ui->pageMusicList->setItemWidget(listWidgetItem, listItemBox);
}
3.4.4、支持 hover 效果
ListItemBox 添加到 CommonPage 中的 QListWidget 之后,自帶 hover 效果,但是背景顏色和界面不太搭配,此處重新實現 hover 效果,此處重寫 enterEvent 和 leaveEvent 來實現 hover 效果。
// listitembox.h 新增
protected:
void enterEvent(QEvent *event);
void leaveEvent(QEvent *event);// listitembox.cpp 新增
void ListItemBox::enterEvent(QEvent *event)
{(void)event;setStyleSheet("background-color:#EFEFEF");
}
void ListItemBox::leaveEvent(QEvent *event)
{(void)event;setStyleSheet("");
}
3.5、自定義 MusicSlider
由于 QT 內置的 Horizontal Slider (水平滑竿)不是很好看,該控件也采用自定義。
① 添加?個設計師界面,objectName 修改為 MusicSlider,geometry 修改為 800*20。
② 拖拽?個 QFrame,objectName 修改為 inLine,geometry 修改為 [(0,8), 800*4]。
③ 拖拽?個 QFrame,objectName 修改為 outLine,geometry 修改為 [(0,8), 400*4]。
④ 選中 MusicSlider,點擊水平布局。
⑤ inLine 和 outLine 的樣式設置如下:
⑥ 打開 QQMusic.ui,選中 progressBar 清除之前樣式,將 progressBar 提升為 MusicSlider ,運行程序就能看到效果。
3.6、自定義 VolumeTool
3.6.1、VolumeTool 控件分析
音量調節控件本來也可以使用 Qt 內置的垂直滑桿來代替,只是垂直滑桿不好看,因此也自定義:
① 內部為類似 MusicSlider 控件 + 小圓球,圓球實際為一個 QPushButton[661630]
② 音量大小文本顯示,實際為 QLabel
③ QPushButton ,點擊之后在靜?和取消靜音切換
④ 一個倒三角,Qt 未提供三角控件,該控件需要手動繪制,用來提示是播放控制區那個按鈕按下的
3.6.2、VolumeTool 界面布局
3.6.3、界面設置
該控件屬于彈出窗口,即點擊了主界面的音量調節按鈕后,才需要彈出該界面,點擊其他位置該界面自動隱藏。因此在窗口創建時,需要設置窗口為無邊框以及為彈出窗口。
// VolumeTool.cpp 的構造函數中添加如下代碼
#include <QGraphicsDropShadowEffect>VolumeTool::VolumeTool(QWidget *parent) :QWidget(parent),ui(new Ui::VolumeTool)
{ui->setupUi(this);setWindowFlags(Qt::Popup | Qt::FramelessWindowHint|Qt::NoDropShadowWindowHint);// 在windows上,設置透明效果后,窗?需要加上Qt::FramelessWindowHint格式// 否則沒有控件位置的背景是??的// 由于默認窗?有陰影,因此還需要將窗?的原有的陰影去掉,窗?需要加上Qt::NoDropShadowWindowHintsetAttribute(Qt::WA_TranslucentBackground);// ?定義陰影效果QGraphicsDropShadowEffect* shadowEffect = newQGraphicsDropShadowEffect(this);shadowEffect->setOffset(0, 0);shadowEffect->setColor("#646464");shadowEffect->setBlurRadius(10);setGraphicsEffect(shadowEffect);// 給按鈕設置圖標ui->silenceBtn->setIcon(QIcon(":/images/volumn.png"));// ?量的默認??是20ui->outLine->setGeometry(ui->outLine->x(), 180 - 36 - 25, ui->outLine->width(), 20);ui->silderBtn->move(ui->silderBtn->x(), ui->outLine->y() - ui->silderBtn->height()/2);ui->volumeRatio->setText("20%");
}
3.6.4、界面創建及彈出
音量調節屬于主界面上元素,因此在 QQMusic 類中需要添加 VolumeTool 的對象,在 initUi 中 new 該類的對象。主界面中音量調節按鈕添加 clicked 槽函數。
// qqmusic.h中新增
#include "volumetool.h"
VolumeTool* volumeTool;// qqmusic.cpp中新增
void QQMusic::initUi()
{// ...// 創建?量調節窗?對象并掛到對象樹volumeTool = new VolumeTool(this);
}void QQMusic::on_volume_clicked()
{// 先要調整窗?的顯?位置,否則該窗?在主窗?的左上?// 1. 獲取該按鈕左上?的圖標QPoint point = ui->volume->mapToGlobal(QPoint(0,0));// 2. 計算volume窗?的左上?位置// 讓該窗?顯?在?標點擊的正上?// ?標位置:減去窗?寬度的?半,以及?度恰巧就是窗?的左上?QPoint volumeLeftTop = point - QPoint(volumeTool->width()/2, volumeTool->height());// 微調窗?位置volumeLeftTop.setY(volumeLeftTop.y()+30);volumeLeftTop.setX(volumeLeftTop.x()+15);// 3. 移動窗?位置volumeTool->move(volumeLeftTop);// 4. 將窗?顯?出來volumeTool->show();
}
3.6.5、繪制三角
由于 Qt 中并未給出三角控件,因此三角需要手動繪制,故在 VolumeTool 類中重寫 paintEvent 事件函數。
// volumetool.h中新增
void paintEvent(QPaintEvent *event);// volumetool.cpp中新增
#include <QPainter>void VolumeTool::paintEvent(QPaintEvent *event)
{(void)event;// 1. 創建繪圖對象QPainter painter(this);// 2. 設置抗鋸?painter.setRenderHint(QPainter::Antialiasing, true);// 3. 設置畫筆// 沒有畫筆時:畫出來的圖形就沒有邊框和輪廓線painter.setPen(Qt::NoPen);// 4. 設置畫刷顏?painter.setBrush(Qt::white);// 創建?個三?形QPolygon polygon;polygon.append(QPoint(30, 300));polygon.append(QPoint(70, 300));polygon.append(QPoint(50, 320));// 繪制三?形painter.drawPolygon(polygon);
}
四、結束語
今天內容就到這里啦,時間過得很快,大家沉下心來好好學習,會有一定的收獲的,大家多多堅持,嘻嘻,成功路上注定孤獨,因為堅持的人不多。那請大家舉起自己的小手給博主一鍵三連,有你們的支持是我最大的動力💞💞💞,回見。