🌹 作者: 云小逸
🤟 个人主页: 云小逸的主页
🤟 motto: 要敢于一个人默默的面对自己,强大自己才是核心。不要等到什么都没有了,才下定决心去做。种一颗树,最好的时间是十年前,其次就是现在!学会自己和解,与过去和解,努力爱自己。希望春天来之前,我们一起面朝大海,春暖花开!
🥇 专栏:
- 动态规划
- C 语言
- C++
- Java 语言
- Linux 编程
- 算法
- 待续…
文章目录
- 📚 前言
- 一、BtForm
- 1. BtForm界面设计
- 2. BtForm类中实现
- 3. 核心要点总结
- 二、推荐页面
- 1. 推荐页面分析
- 2. 推荐页布局
- 3. 自定义RecBox
- 4. 自定义RecBoxItem
- 5. RecBox添加RecBoxItem
- 6. RecBox中btUp和btDown按钮clicked处理
- 7. 核心要点总结
- 三、自定义CommonPage
- 1. CommonPage页面分析
- 2. CommonPage页面布局
- 3. CommonPage界面设置和显示
- 4. 核心要点总结
- 四、自定义ListItemBox
- 1. ListItemBox页面分析
- 2. ListItemBox页面布局
- 3. ListItemBox显示测试
- 4. 支持hover效果
- 5. 核心要点总结
- 五、自定义MusicSlider
- 1. 功能与设计
- 2. 交互实现
- 3. 核心要点总结
- 六、自定义VolumeTool
- 1. 控件分析
- 2. 界面布局
- 3. 界面设置与交互
- 4. 核心要点总结
- 📣 结语
📚 前言
在Qt开发里,自定义控件是极为关键的,它能够帮助我们打造出交互性强且界面精美的应用程序。在QQMusic项目中,有几个重要的自定义控件,像BtForm、推荐页相关控件、CommonPage、ListItemBox、MusicSlider和VolumeTool等。下面我会详细解释这些控件的设计思路、实现方式以及关键代码,让你能更好地理解它们。
一、BtForm
1. BtForm界面设计
- 功能定位:这个控件在音乐播放器的左侧,充当导航按钮。它不仅有图标和文本,还有动画效果。点击它能切换页面,同时会有跳动的竖条显示,提示你当前选中的是哪个按钮。
- 界面组成:
- 整体控件大小是200*35 ,有个
btStyle
容器Widget。在这个容器里,btIcon
用来显示图标,btText
显示文本,lineBox
是动画容器,里面有4个QLabel,这4个QLabel能实现跳动竖条的效果。 - 界面采用水平布局,边距和间距都设为0。当鼠标放到按钮上时,背景色会变成#D8D8D8,让你知道鼠标移到这里了。
- 整体控件大小是200*35 ,有个
- 提升操作:在
bodyLeft
里,把QWidget提升为BtForm自定义控件。这样就能通过属性来设置图标、文本和对应的页面ID,方便后续点击按钮切换页面。
2. BtForm类中实现
- 数据设置:
// btform.h
class BtForm : public QWidget {Q_OBJECT
public:int id = 0; // 按钮对应的页面IDvoid setIcon(QString btIcon, QString btText, int mid) {ui->btIcon->setPixmap(QPixmap(btIcon));ui->btText->setText(btText);this->id = mid;}
};
这段代码定义了BtForm
类,setIcon
方法用于设置按钮的图标、文本和对应的页面ID。具体来说,ui->btIcon->setPixmap(QPixmap(btIcon));
把传入的图标路径对应的图片设置给btIcon
;ui->btText->setText(btText);
把传入的文本设置给btText
;this->id = mid;
则记录下这个按钮对应的页面ID,方便后续点击按钮时能准确切换到相应页面。
- 点击响应:
// btform.cpp
void BtForm::mousePressEvent(QMouseEvent *event) {Q_UNUSED(event);ui->btStyle->setStyleSheet("#btStyle{ background:rgb(30,206,154); color:#F6F6F6; }");emit click(id);
}
mousePressEvent
方法是在鼠标点击按钮时触发。Q_UNUSED(event);
是为了避免编译器警告,表示我们忽略这个事件参数。ui->btStyle->setStyleSheet("#btStyle{ background:rgb(30,206,154); color:#F6F6F6; }");
把按钮的背景色设置为绿色,文本颜色设置为白色,让你能直观看到按钮被点击了。emit click(id);
会发射一个点击信号,并且把按钮对应的页面ID传递出去,这样上层程序就能根据这个ID来切换页面。
- 动画效果:
// btform.cpp(构造函数)
BtForm::BtForm(QWidget *parent) : QWidget(parent) {QPropertyAnimation *animationLine1 = new QPropertyAnimation(ui->line1, "geometry", this);animationLine1->setDuration(1500);animationLine1->setKeyValueAt(0, QRect(0, 15, 2, 0));animationLine1->setKeyValueAt(0.5, QRect(0, 0, 2, 15));animationLine1->setKeyValueAt(1, QRect(0, 15, 2, 0));animationLine1->setLoopCount(-1);animationLine1->start();// 类似地为line2、line3、line4设置动画
}
在构造函数里,为line1
创建了一个QPropertyAnimation
对象。setDuration(1500);
设置动画持续时间为1500毫秒。setKeyValueAt
方法设置了动画的关键帧,在开始时(0时刻)竖条隐藏,到中间(0.5时刻)竖条完全显示,结束时(1时刻)竖条又隐藏。setLoopCount(-1);
表示动画无限循环。这样,竖条就会一直跳动,增强了界面的动态感。对于line2
、line3
、line4
也会做类似的动画设置。
- 动画显示控制:
// btform.cpp
void BtForm::showAnimal(bool isShow) {if (isShow) {ui->lineBox->show();} else {ui->lineBox->hide();}
}
showAnimal
方法用来控制lineBox
的显示或隐藏。如果传入的isShow
为true
,就显示lineBox
,也就是显示跳动的竖条;如果为false
,就隐藏。默认情况下,本地下载按钮的动画是显示的。
3. 核心要点总结
- 数据绑定:通过
setIcon
方法把图标、文本和页面ID关联起来,为按钮的功能实现奠定基础。 - 点击交互:重写
mousePressEvent
方法处理点击事件,改变按钮外观并发射信号,实现页面切换的交互逻辑。 - 动画增强:利用
QPropertyAnimation
实现竖条的动画效果,让界面更有活力。 - 灵活控制:通过
showAnimal
方法能根据需要灵活控制动画的显示。
二、推荐页面
1. 推荐页面分析
- 布局结构:推荐页面有“推荐”“今日为你推荐”“你的歌曲补给站”这些文本。中间是轮播图区域,能左右翻页,鼠标悬停在上面还有动画效果。
- 核心组件:
QScrollArea
:它就像一个大容器,把所有推荐内容都装在里面,还支持滚动操作,方便你查看更多内容。- 自定义
RecBox
控件:里面有左右翻页按钮,还有推荐内容区域,负责管理推荐内容的显示和切换。 RecBoxItem
控件:是单个的推荐项,鼠标放上去时,里面的图片会往上移动。
2. 推荐页布局
- 层级结构:
- 在
recPage
页面添加了QScrollArea
,它内部采用垂直布局。里面有标题标签,还有两个RecBox
,分别对应“今日为你推荐”和“你的歌曲补给站”。 RecBox
包含左右翻页按钮(btUp
/btDown
)和内容区域(recListUp
/recListDown
),采用水平布局。
- 在
- 样式设置:翻页按钮的背景图是
up_page.png
/down_page.png
,鼠标悬停在按钮上时,背景色会变成#1ECD97,有明显的交互反馈。
3. 自定义RecBox
- 界面布局:
- 左右按钮宽度是30,内容区域分上下两行,每行能显示4个
RecBoxItem
。 - 通过
initRecBoxUi
方法接收数据和行数(1行或2行),动态生成推荐项。
- 左右按钮宽度是30,内容区域分上下两行,每行能显示4个
// recbox.cpp
void RecBox::initRecBoxUi(const QJsonArray &data, int rows) {// 清空旧内容clearRecBox();// 根据行数和数据生成推荐项for (int i = 0; i < data.size(); ++i) {if (rows == 1) {// 处理一行的情况createRecBoxItem(data[i].toObject(), 0, i);} else {// 处理两行的情况int row = i / 4;int col = i % 4;createRecBoxItem(data[i].toObject(), row, col);}}
}
initRecBoxUi
方法首先调用clearRecBox
清空旧的推荐项,避免重复显示。然后根据传入的行数和数据来生成推荐项。如果是一行显示的情况,就直接调用createRecBoxItem
添加到相应位置;如果是两行显示,就计算出每个推荐项所在的行和列,再调用createRecBoxItem
添加。
- 数据处理:
- 使用
QJsonArray
存储图片路径和文本,通过std::random_shuffle
随机打乱顺序后分组显示,这样每次打开看到的推荐内容顺序都不一样。 createRecBoxItem
方法根据行数和分组索引添加推荐项到上下布局。
- 使用
// recbox.cpp
void RecBox::createRecBoxItem(const QJsonObject &obj, int row, int col) {RecBoxItem *item = new RecBoxItem(this);item->setItemData(obj["path"].toString(), obj["text"].toString());if (row == 0) {ui->recListUp->addWidget(item, 0, col);} else {ui->recListDown->addWidget(item, 0, col);}
}
createRecBoxItem
方法创建一个RecBoxItem
对象,调用setItemData
方法设置图片路径和文本。然后根据行号判断是添加到上面的布局(recListUp
)还是下面的布局(recListDown
)。
4. 自定义RecBoxItem
- 界面组成:
musicImageBox
:用来显示图片和点击按钮,鼠标放上去时会显示小手图标,提示你可以点击。recBoxItemText
:显示推荐文本,文本是居中对齐的。
- 动画效果:
// recboxitem.cpp
bool RecBoxItem::eventFilter(QObject *watched, QEvent *event) {if (watched == ui->musicImageBox) {int imgWidth = ui->musicImageBox->width();int imgHeight = ui->musicImageBox->height();if (event->type() == QEvent::Enter) {QPropertyAnimation *anim = new QPropertyAnimation(ui->musicImageBox, "geometry");anim->setDuration(100);anim->setStartValue(QRect(9, 10, imgWidth, imgHeight));anim->setEndValue(QRect(9, 0, imgWidth, imgHeight));anim->start();connect(anim, &QPropertyAnimation::finished, anim, &QObject::deleteLater);} else if (event->type() == QEvent::Leave) {QPropertyAnimation *anim = new QPropertyAnimation(ui->musicImageBox, "geometry");anim->setDuration(150);anim->setStartValue(QRect(9, 0, imgWidth, imgHeight));anim->setEndValue(QRect(9, 10, imgWidth, imgHeight));anim->start();connect(anim, &QPropertyAnimation::finished, anim, &QObject::deleteLater);}return true;}return QObject::eventFilter(watched, event);
}
eventFilter
方法是个事件过滤器,用来拦截鼠标进入和离开musicImageBox
的事件。当鼠标进入时,创建一个动画,让图片在100毫秒内从下移10px的位置移动到顶部;当鼠标离开时,创建另一个动画,让图片在150毫秒内从顶部移动回下移10px的位置。动画结束后,通过connect
函数连接finished
信号和deleteLater
槽,释放动画对象的资源。
5. RecBox添加RecBoxItem
- 数据准备:
// qqmusic.cpp
QJsonArray QQMusic::randomPiction() {QVector<QString> imgNames;imgNames << "001.png" << "002.png" << ...;std::random_shuffle(imgNames.begin(), imgNames.end());QJsonArray objArray;for (int i = 0; i < imgNames.size(); ++i) {QJsonObject obj;obj.insert("path", ":/images/rec/" + imgNames[i]);obj.insert("text", QString("推荐-%1").arg(i, 3, 10, QChar('0')));objArray.append(obj);}return objArray;
}
randomPiction
方法创建一个QVector
存储图片名称,然后用std::random_shuffle
打乱顺序。接着创建QJsonObject
,把图片路径和推荐文本插入到对象中,再把对象添加到QJsonArray
里。最后返回这个QJsonArray
,这样就得到了随机顺序的推荐数据。
- 分组逻辑:
- 上行RecBox(1行4列)和下行RecBox(2行4列)根据行数动态分配推荐项到上下布局。也就是说,根据不同的行数要求,把推荐项合理地放到对应的布局里显示。
6. RecBox中btUp和btDown按钮clicked处理
- 翻页逻辑:
// recbox.cpp
void RecBox::onBtUpClicked() {currentIndex = (currentIndex - 1 + groupCount) % groupCount;updateRecBox();
}void RecBox::onBtDownClicked() {currentIndex = (currentIndex + 1) % groupCount;updateRecBox();
}void RecBox::updateRecBox() {// 计算当前组的数据范围int start = currentIndex * 8;int end = qMin(start + 8, data.size());QJsonArray currentData;for (int i = start; i < end; ++i) {currentData.append(data[i]);}initRecBoxUi(currentData, currentIndex < groupCount - 1 ? 2 : 1);
}
onBtUpClicked
方法在点击上一页按钮时调用,通过(currentIndex - 1 + groupCount) % groupCount
计算新的索引,保证索引不会越界。onBtDownClicked
方法在点击下一页按钮时调用,用(currentIndex + 1) % groupCount
计算新索引。updateRecBox
方法根据新索引计算当前组的数据范围,把这些数据添加到currentData
中,然后调用initRecBoxUi
方法更新显示内容。如果不是最后一组,就显示两行;如果是最后一组,就根据实际情况显示一行或两行。
- 性能优化:更新前清除旧元素,避免重复添加,确保界面显示最新分组内容。这样可以提高界面的响应速度,避免出现显示混乱的问题。
7. 核心要点总结
- 布局设计:采用分层布局和自定义控件组合,构建出推荐页的整体结构。
- 数据处理:用
QJsonArray
存储和处理数据,随机打乱和分组显示增加了内容的多样性。 - 交互体验:通过事件过滤器和
QPropertyAnimation
实现鼠标悬停动画,提升了用户的交互感受。 - 性能保障:合理处理翻页逻辑和进行性能优化,保证了界面的流畅性和响应速度。
三、自定义CommonPage
1. CommonPage页面分析
- 适用场景:适用于“我喜欢”“本地下载”“最近播放”等页面,这些页面布局相同,但显示的数据不同。
- 界面结构:
- 标题QLabel:显示页面的标题,比如“本地音乐”。
musicPlayBox
:里面有封面图和“播放全部”按钮。listLabelBox
:显示歌曲信息的标题,像名称、歌手、专辑等。pageMusicList
:使用QListWidget来显示歌曲列表。
2. CommonPage页面布局
- 控件配置:
- 标题标签高度是30,封面图标签宽度是150,“播放全部”按钮尺寸是100*30,采用垂直弹簧布局,能自适应空间。
- 列表标签采用水平布局,歌曲信息列对齐显示,让界面看起来更整齐。
- 样式设置:按钮悬停时背景色变成#1ECD97,圆角为10px,增强了按钮的美观度和交互性。
3. CommonPage界面设置和显示
- 初始化方法:
// commonpage.cpp
void CommonPage::setCommonPageUI(const QString &title, const QString &image) {ui->pageTittle->setText(title);ui->musicImageLabel->setPixmap(QPixmap(image));ui->musicImageLabel->setScaledContents(true);
}
setCommonPageUI
方法用于设置页面的标题和封面图。ui->pageTittle->setText(title);
把传入的标题设置给标题标签;ui->musicImageLabel->setPixmap(QPixmap(image));
把传入的图片路径对应的图片设置给封面图标签;ui->musicImageLabel->setScaledContents(true);
让封面图自动缩放填充,保证图片显示效果。
- 页面关联:在
QQMusic::initUI
中设置三个页面的标题和背景图,通过QStackedWidget
管理页面切换。QStackedWidget
就像一个页面管理器,能方便地在不同页面之间切换。
4. 核心要点总结
- 复用设计:采用通用布局设计,通过自定义控件实现页面复用,减少了代码的重复编写。
- 初始化便捷:利用
setCommonPageUI
方法进行页面初始化,能快速设置标题和封面图。 - 页面管理:通过
QStackedWidget
管理页面切换,提高了多页面显示的效率。
四、自定义ListItemBox
1. ListItemBox页面分析
- 功能:作为QListWidget的列表项,用来显示歌曲信息和收藏按钮。
- 组件构成:
musicNameBox
:里面有收藏按钮(likeBtn)、歌曲名称、VIP/SQ标签。musicSingerBox
:显示歌手名称。albumBox
:显示专辑名称。- 采用水平弹簧控制布局对齐,让界面元素排列更整齐。
2. ListItemBox页面布局
- 尺寸:整体尺寸是800*45,采用水平布局,分为三部分,宽度分别是380、200和剩余空间,合理分配空间显示不同信息。
- 样式设置:
- VIP/SQ标签有边框和颜色区分,能让你快速识别歌曲的特殊属性。收藏按钮无边框,鼠标悬停时背景色变成#EFEFEF,有明显的交互反馈。
3. ListItemBox显示测试
- 集成到CommonPage:
// commonpage.cpp
void CommonPage::setCommonPageUI(const QString &title, const QString &image) {// ...ListItemBox* listItemBox = new ListItemBox(this);QListWidgetItem* listWidgetItem = new QListWidgetItem(ui->pageMusicList);listWidgetItem->setSizeHint(QSize(ui->pageMusicList->width(), 45));ui->pageMusicList->setItemWidget(listWidgetItem, listItemBox);// ...
}
在CommonPage::setCommonPageUI
方法中,创建一个ListItemBox
对象,同时创建一个QListWidgetItem
对象。listWidgetItem->setSizeHint(QSize(ui->pageMusicList->width(), 45));
设置列表项的大小提示。最后通过ui->pageMusicList->setItemWidget(listWidgetItem, listItemBox);
把ListItemBox
添加到QListWidget
中显示。
4. 支持hover效果
// listitembox.cpp
void ListItemBox::enterEvent(QEvent *event) {Q_UNUSED(event);setStyleSheet("background-color:#EFEFEF");
}void ListItemBox::leaveEvent(QEvent *event) {Q_UNUSED(event);setStyleSheet("");
}
enterEvent
方法在鼠标进入列表项时触发,把列表项的背景色设置为#EFEFEF。leaveEvent
方法在鼠标离开列表项时触发,清除背景色设置,让列表项恢复原来的样子。这样能给用户明显的视觉反馈,知道鼠标当前的位置。
5. 核心要点总结
- 布局合理:设计合理的界面布局,清晰显示歌曲信息和收藏按钮。
- 集成方便:通过设置大小提示和添加到QListWidget,能方便地将ListItemBox集成到CommonPage中。
- 交互增强:重写鼠标事件方法,实现hover效果,提升了用户的交互体验。
五、自定义MusicSlider
1. 功能与设计
- 替代原生滑杆:自定义了一个水平进度条,由轨道(inLine)和进度条(outLine)组成,替代了原生的滑杆,能更好地满足特定的设计需求。
- 视觉效果:
- 轨道背景色是#EBEEF5,进度条背景色是#1ECC94,没有边界,看起来更简洁美观。
- 尺寸是800*20,进度条高度为4px,比例协调。
2. 交互实现
// musicslider.cpp
void MusicSlider::mousePressEvent(QMouseEvent *event) {currentPos = event->pos().x();moveSilder();
}void MusicSlider::mouseMoveEvent(QMouseEvent *event) {if (event->buttons() & Qt::LeftButton) {currentPos = event->pos().x();moveSilder();}
}void MusicSlider::mouseReleaseEvent(QMouseEvent *event) {Q_UNUSED(event);moveSilder();emit setMusicSliderPosition(currentPos * 1.0 / width());
}void MusicSlider::moveSilder() {currentPos = qMax(0, qMin(currentPos, width()));ui->outLine->setGeometry(0, 8, currentPos, 4);
}
- 鼠标事件:
mousePressEvent
在鼠标按下时记录鼠标相对于控件的x坐标,并调用moveSilder
方法更新进度条。mouseMoveEvent
在鼠标移动且左键按下时,同样更新坐标并调用moveSilder
。mouseReleaseEvent
在鼠标释放时,调用moveSilder
更新进度条,然后发射setMusicSliderPosition
信号,传递当前进度条的比例。 - 信号槽:通过发射信号,关联到
QQMusic
更新播放位置,实现了进度条和音乐播放位置的同步。 - 进度条更新:
moveSilder
方法把currentPos
限制在0到控件宽度范围内,然后根据这个位置更新outLine
的几何位置,也就是更新进度条的显示长度。
3. 核心要点总结
- 视觉优化:自定义水平进度条,优化了视觉效果,让界面更美观。
- 交互实现:通过重写鼠标事件方法,实现了进度条的拖拽操作,方便用户控制音乐播放进度。
- 功能交互:利用信号槽机制,将进度信息传递给上层模块,实现了进度条和音乐播放的交互。
六、自定义VolumeTool
1. 控件分析
- 功能:是一个音量调节弹出窗口,包含静音按钮、滑杆和音量显示,方便用户调节音量。
- 界面组成:
silenceBtn
:用于切换静音状态,图标会根据静音状态动态变化。sliderBox
:是垂直滑杆,里面有小圆球(sliderBtn)和音量比例显示。- 倒三角绘制:通过
paintEvent
手动绘制提示按钮位置,让界面更直观。
2. 界面布局
- 尺寸:整体尺寸是100*350,采用弹出窗口样式,没有边框,带有阴影,看起来更美观。
- 样式设置:
- 背景色是#FFFFFF,边框圆角为5px,滑杆轨道是#ECECEC,进度条是#1ECC94,滑块圆角为7px,整体界面风格统一。
3. 界面设置与交互
- 弹出逻辑:
// volumetool.cpp
void VolumeTool::showVolumeTool(const QPoint &pos) {move(pos.x() - width() / 2, pos.y());show();
}
showVolumeTool
方法在点击主界面音量按钮时调用,根据传入的位置信息,把窗口移动到按钮下方居中的位置,然后显示窗口。
- 静音按钮逻辑:
// volumetool.cpp
void VolumeTool::onSilenceBtnClicked() {isMuted = !isMuted;ui->silenceBtn->setIcon(isMuted ? QIcon(":/images/silent.png") : QIcon(":/images/volumn.png"));emit setSilence(isMuted);
}
onSilenceBtnClicked
方法在点击静音按钮时触发,切换isMuted
标志位的状态。如果是静音状态,就把按钮图标设置为静音图标;如果是非静音状态,就设置为音量图标。然后发射setSilence
信号,通知QMediaPlayer
设置静音或取消静音。
- 滑杆操作:
// volumetool.cpp
bool VolumeTool::eventFilter(QObject *watched, QEvent *event) {if (watched == ui->sliderBox) {if (event->type() == QEvent::MouseMove) {QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);int y = mouseEvent->pos().y();int height = ui->sliderBox->height();volumeRatio = 1 - y * 1.0 / height;updateSlider();emit setMusicVolume(volumeRatio);}return true;}return QObject::eventFilter(watched, event);
}void VolumeTool::updateSlider() {int height = ui->sliderBox->height();int sliderY = (1 - volumeRatio) * height;ui->sliderBtn->move(0, sliderY - ui->sliderBtn->height() / 2);ui->volumeLabel->setText(QString::number(qRound(volumeRatio * 100)) + "%");
}
eventFilter
方法是事件过滤器,当鼠标在sliderBox
上移动时,计算鼠标位置对应的音量比例volumeRatio
,调用updateSlider
方法更新滑块位置和音量显示,然后发射setMusicVolume
信号,将音量比例传递给播放器更新音量。updateSlider
方法根据音量比例计算滑块的位置,移动滑块并更新音量显示。
4. 核心要点总结
- 界面设计:设计弹出式音量调节窗口,提供了直观的音量调节界面。
- 静音控制:实现了静音按钮的切换逻辑和图标更新,方便用户控制静音状态。
- 滑杆交互:通过事件过滤器处理滑杆操作,实时更新音量显示并发送音量信号,实现了音量的灵活调节。
📣 结语
感谢你耐心看完,这里是我送给你(也给我自己)的几句话:
- 做更好的自己,而不是完美的别人。
- 做你喜欢的事情容易,但做你该做的事,才叫成长。
- 努力让自己变得切实,而不只是一团混乱的情感。
- 有时候放弃容易,但坚持一定很酷。
- 知识不是力量,只有应用知识才是真正的力量。
- 有两种选择活着:忙着死,或忙着活。坚持住就能突出,坚持不住就会被淘汰。你的野心很大,所以你没资格停下来。
- 白天向生活投降,夜晚忠于自己。
如果你觉得我写的不错,记得给我点赞,收藏 和 关注哦(。・ω・。)
让我们一起加油,向美好的未来奔去。让我们从一无所知的新手逐渐成为专家。为自己点赞吧!