QT音乐播放器项目实践:UI界面设计与窗口自定义控件(仿QQ音乐)
在做项目之前,先给大家推荐一个矢量图标的搜索网站-阿里巴巴矢量图标库:iconfont-阿里巴巴矢量图标库我自己在这个项目作业的过程中很多图标都是从该网站获取的,可以直接下载你想要的图片的大小,非常方便.需要注意本文需要有一定的qt基础才可以进行学习,整个项目的制作博主仅对于所有的关键部分和注意点进行解析,界面设计部分比如按钮尺寸,ui布局,图片尺寸大小这种美工部分博主仅给出自己做的时候的参考图或
在做项目之前,先给大家推荐一个矢量图标的搜索网站-阿里巴巴矢量图标库:iconfont-阿里巴巴矢量图标库
我自己在这个项目作业的过程中很多图标都是从该网站获取的,可以直接下载你想要的图片的大小,非常方便.
需要注意本文需要有一定的qt基础才可以进行学习,整个项目的制作博主仅对于所有的关键部分和注意点进行解析,界面设计部分比如按钮尺寸,ui布局,图片尺寸大小这种美工部分博主仅给出自己做的时候的参考图或参考qss代码(不好看勿喷,博主没有艺术细菌T_T,同时博主也是第一次尝试使用Qt做比较大一些的项目).读者对于美工部分可以按照自己的喜好进行设置布局.(使用的Qt版本为5.14.2);
一.界面开发
1.1主界面简要浏览
本文结束时最终要实现的结果页面:

我喜欢,本地下载,最近播放界面如下:

1.2界面开发
根据上面的主界面浏览,我们可以先搭建一个基础的框架出来如下:

这个窗口的大小我们这里使用的是1040*700,读者可自行选择自己喜欢的大小进行布局,只要最终的效果类似图片所示即可,下面是全部控件及其父子关系的示意图:



也就是说我们的窗口大体分为三个部分head,bodyleft与bodyright.接下来便迎来了我们的第一个问题,我们知道,常见的音乐播放器是没有windows边框的,所以此时我们就需要先为窗口设置无边框状态:
1.3为界面设置无边框状态
因为是ui界面初始就要有的状态,所以我们定义一个public函数initUi,把所有的初始化界面工作统一放到这个函数中,而设置无边框状态的方法为:
void SekaiMusic::initUi()
{
this->setWindowFlag(Qt::FramelessWindowHint);
}
此时再次执行程序,我们会发现边框虽然去除了,但是我们也无法对窗口进行关闭和拖动的操作了.关闭好解决,我们可以直接为quit按钮添加一个槽函数用来关闭窗口:
void SekaiMusic::on_quit_clicked()
{
this->close();
}
但是如何解决窗口无法拖动的问题呢,我们知道一般我们拖动时,是鼠标左键按下并移动鼠标.而此时鼠标与窗口左上角的相对位置是不变的:

那我们便可以通过下面的方式解决这个问题:鼠标左键按下时记录鼠标与窗口左上角的相对位置,使用一个private成员QPoint类型名为dragPosition对象记录这个相对位置值,然后重写mouseMoveEvent事件即可完成窗口对拖动事件的响应:
void SekaiMusic::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() == Qt::LeftButton)
{
//注button无法处理移动事件,buttons更为合适,可以参考官方文档
this->move(event->globalPos() - dragPosition);
return;
}
//其他事件默认处理
QWidget::mouseMoveEvent(event);
}
void SekaiMusic::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
//记录相对位置
dragPosition = event->globalPos() - this->frameGeometry().topLeft();
return;
}
//其他事件默认处理
QWidget::mousePressEvent(event);
}
1.4为窗口添加阴影
为窗口添加阴影效果,因为也属于初始化ui界面的工作,所以我们放到initUi中进行.这里需要使用QGraphicsDropShadowEffect进行阴影相关属性的调整与设置:
//添加阴影效果
QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect(this);
shadowEffect->setColor("#000000"); // 阴影颜色:黑色
shadowEffect->setBlurRadius(10); // 阴影模糊半径
shadowEffect->setOffset(0,0); // 阴影偏移值
this->setGraphicsEffect(shadowEffect);
但是添加完毕之后我们会发现效果不太符合预期:

(注意此时用于控制background的布局器必须有一定间隔,最好是默认的,否则背景框会直接把主窗口的阴影效果完全挡住,没有黑边效果)(见上方右图).
因为在qt中默认情况下,QGraphicsDropShadowEffect 的阴影会基于窗口的不透明区域计算。如果窗口背景不透明,阴影会直接附着在窗口矩形边缘,效果生硬.设置透明背景后,阴影可以 柔和过渡到透明区域,视觉效果更自然。所以我们要为窗口设置不透明效果:
//设置窗口背景透明
this->setAttribute(Qt::WA_TranslucentBackground);
接下来的效果就符合预期了(明显有了一层阴影效果):

1.5为界面添加图片资源与设置样式
导入图片资源(qrc):
1.5.1处理head部分
我们先来处理head的部分(图片资源文件会在整个项目介绍写完后上传gitee,读者有需要可以自取,也可以自行于网上搜索图片资源):



1.5.2处理播放控制区部分



后面我自己觉得红色不太好看,就换成银灰色圆角边框了对于那些按钮,设置完之后的示意图如下:

二.自定义控件
2.1BtForm控件
我们注意到,上图中左边的框内最终要实现如下效果:

这里它既有图片,又有文本,还有后面音乐起伏的动画效果,显然qt中默认是没有这样的控件的,所以我们新建一个qt设计师类实现我们的自定义类
基本样式
图片与文本部分我们均使用一个QLabel,而后面因为是四个长矩形,所以我们使用QWidget然后在这个Widget中放入四个短长矩形,并且为其设置白色qss背景样式,同时为该背景控件设置鼠标放上时的黑色遮罩:
//lineBox:
QLabel{
background-color : white;
}
//btStyle
#btStyle:hover{
background-color : rgba(161, 161, 161,0.7);
}
搭建出如下样式就差不多啦:

下面是界面控件的从属关系:
那么搭建完毕之后,我们还有两个事情要处理,一个是为每个QLabel添加图片和设置文本,还有一个是当我们选中某一栏之后,它的鼠标放置阴影遮罩要消失,换为浅蓝色的背景,同时将主界面中的stackedWidget切换到对应的标签页.
这里我们给一个实现思路,对于BtForm:
void setIconAndText(const QString& path,const QString& text,int pageId);
//参数从左向右为:图片路径,设置的文本,需要翻到的页码
//我们在BtForm中添加一个PageId的私有成员对象接收上面的pageId;
int pageId();//主界面切换页面时需要BtForm提供对应页
void clearBg();//为主函数提供btStyle的qss清除功能
signal:
void btClick();//当控件被按下时,通过信号通知主窗口,清除其他btform的背景色同时切换页面
void BtForm::setIconAndText(const QString &path, const QString &text,int pageId)
{
//设置当前自定义控件的图标和文本
ui->btIcon->setPixmap(QPixmap(path));
ui->btText->setText(text);
this->PageId = pageId;
}
void BtForm::clearBg()
{
//设置BtStyle的qss样式
ui->btStyle->setStyleSheet("#btStyle:hover{background-color : rgba(161, 161, 161,0.7);}");
}
int BtForm::pageId()
{
return PageId;
}
void BtForm::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
//当左键按下时才可以选中
ui->btStyle->setStyleSheet("#btStyle{background-color:#66FFFF;}");
emit btClick(PageId);
return;
}
QWidget::mousePressEvent(event);
}
//按下时我们发送btClick信号同时改变背景颜色
void BtForm::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
//当左键按下时才可以选中
ui->btStyle->setStyleSheet("#btStyle{background-color:#66FFFF;}");
emit btClick(PageId);
return;
}
QWidget::mousePressEvent(event);
}
那么在主窗口,因为我们以后还会有自定义信号需要主窗口去为其绑定对应槽函数处理信号,我们这里直接写一个函数在主界面的构造函数中调用:
void SekaiMusic::connectSignalsAndSlots()
{
//为btClick设置函数
connect(ui->Rec,&BtForm::btClick,this,&SekaiMusic::btClickSlot);
connect(ui->music,&BtForm::btClick,this,&SekaiMusic::btClickSlot);
connect(ui->audio,&BtForm::btClick,this,&SekaiMusic::btClickSlot);
connect(ui->like,&BtForm::btClick,this,&SekaiMusic::btClickSlot);
connect(ui->local,&BtForm::btClick,this,&SekaiMusic::btClickSlot);
connect(ui->recent,&BtForm::btClick,this,&SekaiMusic::btClickSlot);
}
//btClick绑定的槽函数
void SekaiMusic::btClickSlot(int PageId)
{
//处理翻页以及qss颜色效果
ui->stackedWidget->setCurrentIndex(PageId);
QList<BtForm*> allBtForm = ui->leftBox->findChildren<BtForm*>();
for(auto btform : allBtForm)
{
if(btform->pageId() != PageId)
{
btform->clearBg();
}
}
}
然后再initUi中调用btForm的setIconAndText:
//设置BtForm的图标和文本
ui->Rec->setIconAndText(":/images/rec.png", "推荐",0);
ui->music->setIconAndText(":/images/music.png", "⾳乐馆",1);
ui->audio->setIconAndText(":/images/radio.png", "电台",2);
ui->like->setIconAndText(":/images/like.png", "我喜欢",3);
ui->local->setIconAndText(":/images/local.png", "本地下载",4);
ui->recent->setIconAndText(":/images/recent.png", "最近播放",5);
当然我们也可以在主界面中为每个BtForm添加圆角效果:
border : none;
border-radius : 15px;
此时我们便有了如下图的效果界面:
动画效果
Qt中QPropertyAnimation类可以提供简单的动画效果,允许对QObject获取派⽣类的可读写属性进⾏动画处理,创建平滑、连续的动画效果,⽐如控件的位置、⼤⼩、颜⾊等属性变化,使⽤时需包含<QPropertyAnimation>。所以这里我们使用这个类来实现效果:
因为我们这里有四个QLabel,界面显示时需要永久显示其动画效果,所以我们这里为BtForm新增四个QPropertyAnimation*来管理这四个线条的动画效果:
privated:
QPropertyAnimation* line1An;
QPropertyAnimation* line2An;
QPropertyAnimation* line3An;
QPropertyAnimation* line4An;
到此先停住,此时我的line1的坐标和宽度位置如下:

所以如果想要实现矩形长度从0-15-0,我们就需要像如下这样进行打关键帧:
line1An = new QPropertyAnimation(ui->line1,"geometry",this);
line1An->setDuration(1500);
line1An->setKeyValueAt(0, QRect(0, 15, 2, 0));
line1An->setKeyValueAt(0.5, QRect(0, 0, 2, 15));
line1An->setKeyValueAt(1, QRect(0, 15, 2, 0));
line1An->setLoopCount(-1);
line1An->start();
(上面示例坐标当时写博客打错了,下面调整持续时间部分的代码是正确的,抱歉)其他如法炮制即可;
line2An = new QPropertyAnimation(ui->line2,"geometry",this);
line2An->setDuration(1500);
line2An->setKeyValueAt(0, QRect(9, 22, 2, 0));
line2An->setKeyValueAt(0.5, QRect(9, 7, 2, 15));
line2An->setKeyValueAt(1, QRect(9, 22, 2, 0));
line2An->setLoopCount(-1);
line2An->start();
line3An = new QPropertyAnimation(ui->line3,"geometry",this);
line3An->setDuration(1500);
line3An->setKeyValueAt(0, QRect(15, 22, 2, 0));
line3An->setKeyValueAt(0.5, QRect(15, 7, 2, 15));
line3An->setKeyValueAt(1, QRect(15, 22, 2, 0));
line3An->setLoopCount(-1);
line3An->start();
line4An = new QPropertyAnimation(ui->line4,"geometry",this);
line4An->setDuration(1500);
line4An->setKeyValueAt(0, QRect(21, 22, 2, 0));
line4An->setKeyValueAt(0.5, QRect(21, 7, 2, 15));
line4An->setKeyValueAt(1, QRect(21, 22, 2, 0));
line4An->setLoopCount(-1);
line4An->start();
切记高度一定不要锁死,不然会出现上下跳动的情况.
但是此时跳动的太整齐了,我们想要它是那种不规律的,所以就要将他们的持续时间Duration设置不同即可:
line1An = new QPropertyAnimation(ui->line1,"geometry",this);
line1An->setDuration(1500);
line1An->setKeyValueAt(0, QRect(3, 22, 2, 0));
line1An->setKeyValueAt(0.5, QRect(3, 7, 2, 15));
line1An->setKeyValueAt(1, QRect(3, 22, 2, 0));
line1An->setLoopCount(-1);
line1An->start();
line2An = new QPropertyAnimation(ui->line2,"geometry",this);
line2An->setDuration(1600);
line2An->setKeyValueAt(0, QRect(9, 22, 2, 0));
line2An->setKeyValueAt(0.5, QRect(9, 7, 2, 15));
line2An->setKeyValueAt(1, QRect(9, 22, 2, 0));
line2An->setLoopCount(-1);
line2An->start();
line3An = new QPropertyAnimation(ui->line3,"geometry",this);
line3An->setDuration(1700);
line3An->setKeyValueAt(0, QRect(15, 22, 2, 0));
line3An->setKeyValueAt(0.5, QRect(15, 7, 2, 15));
line3An->setKeyValueAt(1, QRect(15, 22, 2, 0));
line3An->setLoopCount(-1);
line3An->start();
line4An = new QPropertyAnimation(ui->line4,"geometry",this);
line4An->setDuration(1800);
line4An->setKeyValueAt(0, QRect(21, 22, 2, 0));
line4An->setKeyValueAt(0.5, QRect(21, 7, 2, 15));
line4An->setKeyValueAt(1, QRect(21, 22, 2, 0));
line4An->setLoopCount(-1);
line4An->start();
看看效果(当然也可以设置没有选中的让他的动画效果隐藏,博主这里就不设置了):

2.2推荐页面中的自定义控件设置
我们来看下这个自定义控件的最终样式:

鼠标放上之后音乐图片会有一个向上的抬起动作,离开之后又会恢复原样,同时还具备翻页功能,而且还需要支持上下的滚动条功能.所以我们先在主页面拖入一个scrollArea,然后拖入三个QLabel与两个QWidget(用于后面提升为我们的自定义控件):
=
RecBox自定义控件
ui布局与控件从属关系如下:


因为我们的音乐图标需要加入到RecBox中的recListDownLayout和recListUpLayout中,它有自定义图片上浮动画,还有文本,还要可供点击响应,所以这部分我们也需要自定义.
RecBoxItem控件
ui布局与控件从属关系如下:


RecBoxItem设置图片动画效果
有了上面设置音乐条的经验,这里其实就很容易了.但是我们需要拦截鼠标进入与离开MusciImageBox控件的事件,就必须在RecBoxItem的构造函数中安装事件过滤器:
//拦截事件需要安装事件拦截器
ui->MusicImageBox->installEventFilter(this);
之后我们再重写eventFilter函数来拦截鼠标进入与离开事件即可:
//拦截MusicImageBox的鼠标进入和离开事件
bool RecBoxItem::eventFilter(QObject *obj, QEvent *event)
{
if(obj == ui->MusicImageBox)
{
int width = ui->MusicImageBox->width();
int heigth = ui->MusicImageBox->height();
if(event->type() == QEvent::Enter)
{
//鼠标进入设置动画
QPropertyAnimation* animation = new QPropertyAnimation(ui->MusicImageBox,"geometry");
animation->setDuration(150);
animation->setStartValue(QRect(9,9,width,heigth));//音乐封面的初始位置与长度宽度
animation->setEndValue(QRect(9,0,width,heigth));//因为我这里设置的水平布局器把他往下挤了9px,所以上浮时让其y:-9px即可
animation->setLoopCount(1);
animation->start();
//动画结束释放动画对象
connect(animation,&QPropertyAnimation::finished,this,[=](){
delete animation;
});
}
else if(event->type() == QEvent::Leave)
{
//鼠标离开设置动画
QPropertyAnimation* animation = new QPropertyAnimation(ui->MusicImageBox,"geometry");
animation->setDuration(150);
animation->setStartValue(QRect(9,0,width,heigth));
animation->setEndValue(QRect(9,9,width,heigth));
animation->setLoopCount(1);
animation->start();
//动画结束释放动画对象
connect(animation,&QPropertyAnimation::finished,this,[=](){
delete animation;
});
}
}
//其他交给基类处理
return QObject::eventFilter(obj,event);
}
主界面提取推荐音乐封面与文本传入RecBox
这里我们使用Json来格式化我们的封面路径与歌单名称,通过std::random_shuttle来实现一个随机展示的效果:
QJsonArray SekaiMusic::randomPicture()
{
//打乱推荐的图片与文本
QVector<QString> vectorImage;
vectorImage << "001.png" << "002.png" << "003.png" << "004.png" << "005.png"
<< "006.png" << "007.png" << "008.png" << "009.png" << "010.png"
<< "011.png" << "012.png" << "013.png" << "014.png" << "015.png"
<< "016.png" << "017.png" << "018.png" << "019.png" << "020.png"
<< "021.png" << "022.png" << "023.png" << "024.png" << "025.png"
<< "026.png" << "027.png" << "028.png" << "029.png" << "030.png"
<< "031.png" << "032.png" << "033.png" << "034.png" << "035.png"
<< "036.png" << "037.png" << "038.png" << "039.png" << "040.png";
std::random_shuffle(vectorImage.begin(),vectorImage.end());
//将打乱的Qvector放入recArray
QJsonArray recArray;
for(int i = 0;i < vectorImage.size();i++)
{
QJsonObject obj;
QString path = QString(":/images/rec/") + vectorImage[i];
QString text = QString("推荐-%1").arg(i + 1,3,10,QChar('0'));
obj.insert("path",path);
obj.insert("text",text);
//追加到Json数组中
recArray.append(obj);
}
return recArray;
}
因为我们没有连接网络,所以这里只能硬编码的形式把预先存储好的图片加载进来,后续加入数据库功能时这里会再改正.
RecBox获取主界面提供的歌单序列并初始化自己的上下Layout
//添加成员对象:
int row = 1;
int col = 2;
QJsonArray imageLists;
//主界面调用该初始化函数时记得设置随机数种子srand(time(nullptr))
void RecBox::initRecBox(QJsonArray imageLists,int row)
{
this->imageLists = imageLists;
if(row == 2)
{
this->col = 8;
this->row = row;
}
else
{
ui->recListDown->hide();
}
for(int i = 0;i < col;i++)
{
RecBoxItem* item = new RecBoxItem();
QJsonObject obj = this->imageLists[i].toObject();
item->setText(obj.value("text").toString());
item->setImage(obj.value("path").toString());
ui->recListUpLayout->addWidget(item);
}
}
//RecBoxItem:
void RecBoxItem::setImage(const QString &imagePath)
{
ui->recMusicImage->setStyleSheet(QString("background-image:url(" + imagePath +");border-radius:10px;"));
}
void RecBoxItem::setText(const QString &recText)
{
ui->recMusicText->setText(recText);
}
这里设置完后,我们上面的推荐区域为1行4列,而下面的补给区域为2行8列,所以必定会出现如下图的问题:

注意到我们上面在当row为2的时候,我们设置col为8.就是为此时埋下了伏笔.我们这里便可以借助col设置的这个8来区分补给站一栏此时是为上面添加元素还是为下面添加元素:
//将上面的recInitBox中的ui->recListUpLayout->addWidget(item);改为如下内容
if(i >= col / 2 && row == 2)
{
ui->recListDownLayout->addWidget(item);
}
else
{
ui->recListUpLayout->addWidget(item);
}
此时便能正确显示界面了:

RecBox实现上下翻页效果
因为每次我们获取的图片数量为4的倍数(我这里准备的是40张图片).所以我们可以基于四给一个总的分组数,用count成员变量来表示.而当前页面我们使用currentIndex来记录.所以我们可以设置两个翻页键的槽函数如下(当然这两个成员变量在RecInitIndex中初始化):
void RecBox::on_btUp_clicked()
{
currentIndex -= 1;
if(currentIndex < 0)
currentIndex = count - 1;
setCurrentIndexImage();
}
void RecBox::on_btDown_clicked()
{
currentIndex = (currentIndex + 1) % count;
setCurrentIndexImage();
}
此时我们需要添加一个新的成员函数来让我们所属当前组的图片被正确设置到RecBox上(把initRecBox中的for循环部分拿出来稍作修改即可):
void RecBox::setCurrentIndexImage()
{
//这样我不用销毁对象直接复用原对象即可
int index = 0;
for(int i = col * currentIndex;i < col + col * currentIndex;i++)
{
QJsonObject obj = this->imageLists[i].toObject();
if(index >= col / 2 && row == 2 && index - col/2 < downLists.size())
{
downLists[index % (col/2)]->setText(obj.value("text").toString());
downLists[index % (col/2)]->setImage(obj.value("path").toString());
}
else
{
upLists[index]->setText(obj.value("text").toString());
upLists[index]->setImage(obj.value("path").toString());
}
index++;
}
}
//更新后的initBox
void RecBox::initRecBox(QJsonArray imageLists,int row)
{
this->imageLists = std::move(imageLists);
if(row == 2)
{
this->col = 8;
this->row = row;
}
else
{
ui->recListDown->hide();
}
currentIndex = 0;
count = this->imageLists.size() / col;//注意原数据被抢走了已经
for(int i = 0;i < col;i++)
{
RecBoxItem* item = new RecBoxItem();
QJsonObject obj = this->imageLists[i].toObject();
item->setText(obj.value("text").toString());
item->setImage(obj.value("path").toString());
if(i >= col / 2 && row == 2)
{
ui->recListDownLayout->addWidget(item);
downLists.push_back(item);//记录插入的对象
}
else
{
ui->recListUpLayout->addWidget(item);
upLists.push_back(item);//同上
}
}
}
ok到这里我们的推荐模块也就先告一段落了:

2.3commonPage我喜欢/本地下载/最近播放页面设置
此页面的布局很简单,一行文本用来标记当前页的作用,一张图片和前者类似,然后一个全部播放按钮,下面是一个ListWidget用来存放音乐曲目,大致布局如下:

样式设计大家可以根据自己的喜好去设置样式,这里我们还需要设置音乐项的展示样式 ListWidgetItem,如下:

我们来认识下这个结构,从左向右一共是三个QLabel,第一个Label用来标识音乐名,我喜欢按钮,以及是否有高级音质SQ,是否有MV.接下来后面两个里面都是QLabel.这明显不是原ListWidget能支持的列表控件,所以我们可以这样布局该自定义控件:


这样添加完自定义对象之后,我们添加一个测试的条目在commPage的构造函数中:
CommonPage::CommonPage(QWidget *parent) :
QWidget(parent),
ui(new Ui::CommonPage)
{
ui->setupUi(this);
//测试添加歌曲条目
ListItemBox* listItemBox = new ListItemBox(this);
QListWidgetItem* listWidgetItem = new QListWidgetItem(ui->pageMusicList);
//需要指定推荐显示尺寸,否则会遵从默认设置,不一定是我们想要的效果
listWidgetItem->setSizeHint(QSize(ui->pageMusicList->width(), 45));
ui->pageMusicList->setItemWidget(listWidgetItem, listItemBox);
}
就有了如下图的效果:

2.4自定义播放进度条
注意到上图的进度条部分了吗,它看起来已经能说的过去了,实现也很简单(当然我们现在只是吧界面的显示部分给弄好,其他逻辑到我们真正导入音乐之后才能开始着手),MusicSlider的样式与控件从属关系如下:


接下来只需要给他提升到主界面对应的Widget即可
2.5自定义音量控制条
当然这个也是需要我们自定义的,思路与上面的进度条差不多,就多了一个按钮与百分比显示的Label,布局如下:


布局固然容易,但是如何去显示这个音量控制器呢,我们想要实现的效果是,鼠标放到主界面的volumn键时弹出音量控制区域,然后鼠标离开音量控制区域时我们关闭弹窗.所以第一步我们要为该控件设置弹窗属性,为了好看,我们像之前那样给主窗口设置圆角阴影那样为这个自定义控件的主QWidget也设置类似的效果:
//设置弹出窗口/无边框/不显示系统默认的投射阴影效果->我们自己设置
setWindowFlags(Qt::Popup | Qt::FramelessWindowHint| Qt::NoDropShadowWindowHint);
//设置背景透明
setAttribute(Qt::WA_TranslucentBackground);
QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect(this);
shadowEffect->setOffset(0, 0);
shadowEffect->setColor("#646464");
shadowEffect->setBlurRadius(10);
setGraphicsEffect(shadowEffect);
然后在主窗口的类中添加一个指向volumnTool对象的指针,在initUi函数中初始化它,同时为volumn添加事件拦截器,以便于我们去实现上面的效果:
//initUi
//添加volumnTool,并挂到对象数上
volumetool = new VolumeTool(this);
//为volumn添加事件过滤器,重写鼠标进入与离开事件,控制volumnTool的显示
ui->volume->installEventFilter(this);
//eventFilter->点击volumn时我们切换静音与非静音
bool SekaiMusic::eventFilter(QObject * obj, QEvent *event)
{
if(obj == ui->volume)
{
//鼠标进入,弹出volumntool,关闭volumntool的方法重写他自己的leaveEvent,也可以不用重写,点击其他地方即可关闭弹窗
if(event->type() == QEvent::Enter)
{
//设置volumnTool的位置,切记不要在构造函数中设置!!!!
QPoint volumnGlobal = ui->volume->mapToGlobal(QPoint(0,0));//获取volumn的全局坐标
QPoint volumnToolPoint = QPoint(volumnGlobal.x() - (volumetool->width() - ui->volume->width())/2,
volumnGlobal.y() - volumetool->height());
volumetool->move(volumnToolPoint);
volumetool->show();
return false;// 让其他事件继续传递
}
else if(event->type() == QEvent::MouseButtonPress)
{
// 上面返回false不一定保证事件继续传递,特别是在有样式表或复杂控件层次时
//此时必须要我们显示调用才行
on_volume_clicked();//volumn的clicked的槽函数
return true; // 点击事件已处理
}
}
//其他事件交给基类方法处理
return QObject::eventFilter(obj,event);
}
注意到上面我们调整了volumn的位置了吗,这是因为如果我们不设置,这个volumnTool会默认弹出到我们整个屏幕的0,0位置.当然我们是想要让其显示在当前音量键的上方的,不过这里volumn默认的gemotry获取的是它自己在当前窗口内的坐标,要想volumn与volumnTool处于同一坐标系进行计算,我们就需要使用mapToGlobal:
这个函数的意思是,你传入一个当前控件内部的坐标点,然后我们返回一个该坐标点在全局坐标中的位置,因为我们这里是想要获取volumn的全局坐标位置,所以我们给一个0,0进去即可.然后为了让其居中显示,可以参考我上面的坐标计算公式.计算出目标坐标将volumnTool移动到该位置然后显示即可达成如下的效果:
好的,到这里我们界面中所有需要自定义控件的部分就暂时告一段落了,下篇文章我们再来看看如何去实现音乐的播放以及滚动歌词的显示.
更多推荐



所有评论(0)