《基于Qt的车载系统项目》
基于Qt的车载系统项目
项目框架
本项目将实现五个功能
- 时间显示
- 音乐播放
- 视频播放
- 天气显示
- 地图显示
第一类就是定时器
第二类就是多媒体模块
第三类就是api的调用
接下来我将介绍功能实现方法,本博客仅介绍核心功能实现方法
,涉及ui美化
以及头文件包含
将不多介绍,大家可以让ai
辅助优化
1.跳转功能
这个应该是本项目最基本的一个功能,我们通过pushbutton
通过点击跳转到对应界面,这里ui设计
就不多介绍了,效果如下
同时我们还需要添加四个ui和h文件,分别对应四个模块,时间模块可以直接在widget下面显示,就不需要创建了
//初始化ui对象
misc=new music();
mp=new map();
vdo=new video();
wea=new weather();
这里以video模块为例,假设我们点击这个按钮进入界面,那我们这个槽函数就应该这么写
void Widget::on_video_button_clicked()
{this->hide();//隐藏widget界面vdo->show();//显示video模块ui
}
在后面我们还要对每个界面增加一个返回按钮
,以至我们可以返回我们的主页面,同样以video模块为例,我们需要定义一个信号
(发出信号,让widget显示 ui
)
void video::on_back_button_clicked()
{emit backToMain();//需要在h文件signal:定义this->hide();
}
widget需要接受信号并处理
connect(vdo,&video::backToMain,this,&Widget::show);
这样就实现了对应的功能
2.时间显示
实现显示就是通过定时器的中断函数更新并显示一次时间(跟stm32的中断有点像…)
//日期
connect(clockTimer, &QTimer::timeout, this, [=]() {QDateTime now = QDateTime::currentDateTime();ui->date_lable->setText(now.toString("yyyy-MM-dd ddd")); // 例:2025-08-13 Wedui->time_lable->setText(now.toString("HH:mm:ss")); // 例:14:35:28
});
// 启动前先手动刷新一次(避免等 1 秒才出现)
QDateTime now = QDateTime::currentDateTime();
ui->date_lable->setText(now.toString("yyyy-MM-dd ddd"));
ui->time_lable->setText(now.toString("HH:mm:ss"));
//启动时钟
clockTimer->start(1000);
3.视频播放
先看看ui
定义一些对象
private:Ui::video *ui; QMediaPlayer *qmp; // 媒体播放器对象,用于播放音视频QMediaPlaylist *qmpl; // 播放列表对象,用于管理多个媒体文件的播放顺序QVideoWidget *videoWidget; // 视频显示控件,用于在界面上显示视频内容
qmp=new QMediaPlayer(this);
qmpl=new QMediaPlaylist(this);
videoWidget=new QVideoWidget(this);
qmp->setVideoOutput(videoWidget);//播放器对象需要配置
qmp->setPlaylist(qmpl);//同上
由于视频比较大,我们需要放在debug文件夹下面
1.音量调节
我们通过verticalSlider
调节音量大小
connect(ui->verticalSlider, &QSlider::valueChanged, this, [=](int value){qmp->setVolume(value); // QMediaPlayer 的音量 0~100});
2.列表加载
void video::loadVideoList()
{// 获取程序所在目录并拼接 music 文件夹QDir dir(QCoreApplication::applicationDirPath() + "/video");QStringList filters;filters << "*.mp4" ;//过滤QStringList files = dir.entryList(filters, QDir::Files);//只筛选文件,不包含文件夹for (const QString &file : files) {// listWidget 显示文件名ui->mp4listWidget->addItem(QFileInfo(file).baseName());// playlist 添加文件qmpl->addMedia(QUrl::fromLocalFile(dir.filePath(file)));}
}
3.播放与点击列表播放
//点击更换播放建图标
connect(ui->on_button, &QPushButton::clicked, this, [=](){if(qmp->state() == QMediaPlayer::PlayingState){qmp->pause();ui->on_button->setIcon(QIcon(":/images/on.png")); // 暂停图标} else {qmp->play();ui->on_button->setIcon(QIcon(":/images/stop.png")); // 播放图标}});
//点击播放
connect(ui->mp4listWidget, &QListWidget::itemClicked, this, [=](QListWidgetItem *item){int index = ui->mp4listWidget->row(item);qmpl->setCurrentIndex(index);qmp->play();ui->on_button->setIcon(QIcon(":/images/stop.png"));ui->lineEdit->setText(item->text());});
第二段代码的作用是:
当用户点击 QListWidget
里的某个视频文件项时:
- 取到它的行号;
- 切换播放列表到这个视频;
- 开始播放;
- 更新按钮图标为“停止”;
- 在输入框显示当前视频的名字。
4.进度条
value:0-100%
pos:0-音乐时长(ms)
duration:完整音乐时长
这样换算就非常容易理解了
//进度条
//播放器播放时更新滑块
connect(qmp, &QMediaPlayer::positionChanged, this, [=](qint64 pos){if(qmp->duration() > 0){int value = static_cast<int>(pos * 100 / qmp->duration());ui->horizontalSlider->setValue(value);}
});//滑块拖动时更新播放器
connect(ui->horizontalSlider, &QSlider::sliderReleased, this, [=](){int value = ui->horizontalSlider->value();if(qmp->duration() > 0){qint64 newPos = value * qmp->duration() / 100;qmp->setPosition(newPos);}
});
4.音乐播放
先看看ui
我们可以发现这个和视频播放很像,无非就多了上一首、下一首,以及mode(单曲循环,随机播放,下一首)
1.进度和音量
其实是一模一样
//音量
connect(ui->verticalSlider, &QSlider::valueChanged, this, [=](int value){player->setVolume(value); // QMediaPlayer 的音量 0~100
});
//进度条
//播放器播放时更新滑块
connect(player, &QMediaPlayer::positionChanged, this, [=](qint64 pos){if(player->duration() > 0){int value = static_cast<int>(pos * 100 / player->duration());ui->horizontalSlider->setValue(value);}
});//滑块拖动时更新播放器
connect(ui->horizontalSlider, &QSlider::sliderReleased, this, [=](){int value = ui->horizontalSlider->value();if(player->duration() > 0){qint64 newPos = value * player->duration() / 100;player->setPosition(newPos);}
});
2.加载列表
void music::loadMusicList()
{
// 获取程序所在目录并拼接 music 文件夹
QDir dir(QCoreApplication::applicationDirPath() + "/music");
QStringList filters;
filters << "*.mp3" << "*.wav";//筛选
QStringList files = dir.entryList(filters, QDir::Files);for (const QString &file : files) {// listWidget 显示文件名ui->mp3listWidget->addItem(QFileInfo(file).baseName());// playlist 添加文件playlist->addMedia(QUrl::fromLocalFile(dir.filePath(file)));
}
}
3.播放模式转换
/播放模式
connect(ui->mode_button, &QPushButton::clicked, this, [=](){switch(playlist->playbackMode()){case QMediaPlaylist::Loop:playlist->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop);//单曲ui->mode_button->setIcon(QIcon(":/images/danqu.png"));break;case QMediaPlaylist::CurrentItemInLoop:playlist->setPlaybackMode(QMediaPlaylist::Random);//随机ui->mode_button->setIcon(QIcon(":/images/suiji.png"));break;case QMediaPlaylist::Random:default:playlist->setPlaybackMode(QMediaPlaylist::Loop);//循环ui->mode_button->setIcon(QIcon(":/images/xunhuan.png"));break;}
4.播放与点击列表播放
connect(ui->on_button, &QPushButton::clicked, this, [=](){if(player->state() == QMediaPlayer::PlayingState){player->pause();ui->on_button->setIcon(QIcon(":/images/on.png")); // 暂停图标} else {player->play();ui->on_button->setIcon(QIcon(":/images/stop.png")); // 播放图标}});//点击播放
connect(ui->mp3listWidget, &QListWidget::itemClicked, this, [=](QListWidgetItem *item){int index = ui->mp3listWidget->row(item);playlist->setCurrentIndex(index);player->play();ui->on_button->setIcon(QIcon(":/images/stop.png"));ui->lineEdit->setText(item->text());
});//list显示播放歌曲
connect(playlist, &QMediaPlaylist::currentIndexChanged, this, [=](int index){if(index >= 0 && index < ui->mp3listWidget->count()){// 高亮当前播放歌曲ui->mp3listWidget->setCurrentRow(index);// 显示歌曲名ui->lineEdit->setText(ui->mp3listWidget->item(index)->text());}
});
5.上一首下一首
connect(ui->prev_button, &QPushButton::clicked, this, [=]() { playlist->previous();});
connect(ui->next_button, &QPushButton::clicked, this, [=]() { playlist->next();});
6.地图显示
看看ui
这个模块主要是 Qt 端 与高德地图 HTML/JS 页面交互,实现输入搜索、联想提示和路线绘制 的逻辑
开发文档
1.初始化地图和网络访问
mapView = new QWebEngineView(this);
manager = new QNetworkAccessManager(this);QString htmlPath = QCoreApplication::applicationDirPath() + "/map/map.html";
mapView->load(QUrl::fromLocalFile(htmlPath));
mapView->setGeometry(10, 10, 671, 651);
mapView->show();
- mapView:嵌入一个 Web 引擎视图,用来显示高德地图的 HTML 页面。
- manager:用于发送 HTTP 请求(调用高德输入提示接口)。
- load:加载本地的
map.html
文件(你之前贴的 HTML/JS)。 - setGeometry/show:设置大小并显示。
2.输入框联想与信号连接
connect(ui->start_lineEdit, &QLineEdit::textChanged, this, &map::searchRoute);
connect(ui->final_lineEdit, &QLineEdit::textChanged, this, &map::searchRoute);
- 当用户在 起点/终点输入框 输入文字时,触发
searchRoute()
- 功能:发送高德 输入提示接口 请求,获取联想建议。
3.联想列表点击事件
connect(ui->start_listWidget, &QListWidget::itemClicked, this, &map::onStartListClicked);
connect(ui->final_listWidget, &QListWidget::itemClicked, this, &map::onEndListClicked);
- 当用户点击联想列表里的某一项时:
- 填充输入框
- 保存选中经纬度
- 调用 JS 接口在地图上添加起点/终点标记
4 .搜索路线按钮
connect(ui->research_button, &QPushButton::clicked, this, &map::on_research_button_clicked);
- 点击“搜索路线”按钮时,调用
on_research_button_clicked()
- 功能:在地图上绘制起点到终点的路线(调用 JS 的
drawRoute()
)。
5.searchRoute() 函数
void map::searchRoute()
{QString start = ui->start_lineEdit->text();QString end = ui->final_lineEdit->text();// 起点联想if(!start.isEmpty()){QUrl startUrl("https://restapi.amap.com/v3/assistant/inputtips");QUrlQuery query;query.addQueryItem("key", AMAP_KEY);query.addQueryItem("keywords", start);query.addQueryItem("city", "全国");startUrl.setQuery(query);QNetworkReply *reply = manager->get(QNetworkRequest(startUrl));connect(reply, &QNetworkReply::finished, [=](){ handleStartTipsReply(reply); });}// 终点联想同理
}
- 作用:向高德输入提示接口发送 HTTP GET 请求
- 参数:
key
:你的高德地图开发者 Keykeywords
:用户输入的文字city
:限制搜索城市,这里是全国
- 返回结果会在
handleStartTipsReply
/handleEndTipsReply
处理。
6.处理联想结果
void map::handleStartTipsReply(QNetworkReply *reply)
{QByteArray data = reply->readAll();reply->deleteLater();QJsonDocument doc = QJsonDocument::fromJson(data);QJsonArray tips = doc.object()["tips"].toArray();ui->start_listWidget->clear();for(auto tip : tips){QString name = tip.toObject()["name"].toString();QString location = tip.toObject()["location"].toString(); // "lng,lat"QListWidgetItem *item = new QListWidgetItem(name, ui->start_listWidget);item->setData(Qt::UserRole, location);ui->start_listWidget->addItem(item);}
}
- 作用:把高德返回的联想结果解析出来,显示到
QListWidget
- location(经纬度)保存到
UserRole
数据中,方便后续在地图上使用。
7.联想列表点击处理
void map::onStartListClicked(QListWidgetItem *item)
{ui->start_lineEdit->setText(item->text());startLocation = item->data(Qt::UserRole).toString(); // 保存选中经纬度mapView->page()->runJavaScript(QString("updateStart('%1')").arg(startLocation));
}
- 更新输入框文本
- 保存 经纬度 到成员变量
startLocation
- 调用 HTML/JS 接口
updateStart()
在地图上添加标记 - 终点处理类似,调用
updateEnd()
。
8.搜索路线按钮处理
void map::on_research_button_clicked()
{if(startLocation.isEmpty() || endLocation.isEmpty()) return;QString js = QString("drawRoute('%1','%2')").arg(startLocation).arg(endLocation);mapView->page()->runJavaScript(js);
}
- 使用保存的经纬度,而不是输入框文字
- 调用 JS
drawRoute(startLoc, endLoc)
绘制路线 - 地图上显示起点、终点和一条红色直线路径。
9.内嵌html源码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>高德地图示例</title>
<script src="https://webapi.amap.com/maps?v=2.0&key=<your_key>"></script>
<style>
html, body, #container { margin:0; padding:0; width:100%; height:100%; }
</style>
</head>
<body>
<div id="container"></div><script>
var map = new AMap.Map('container',{center: [120.1551,30.2741],zoom: 11
});var startMarker, endMarker, routeLine;// 添加起点标记
function addStartMarker(lng, lat, name){if(startMarker) map.remove(startMarker);startMarker = new AMap.Marker({position: [lng, lat],title: name,icon: "https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png"});map.add(startMarker);
}// 添加终点标记
function addEndMarker(lng, lat, name){if(endMarker) map.remove(endMarker);endMarker = new AMap.Marker({position: [lng, lat],title: name,icon: "https://webapi.amap.com/theme/v1.3/markers/n/mark_r.png"});map.add(endMarker);
}// 更新起点(供 Qt 调用)
function updateStart(loc){if(!loc) return;var parts = loc.split(',');if(parts.length !== 2) return;var lng = parseFloat(parts[0]);var lat = parseFloat(parts[1]);if(isNaN(lng) || isNaN(lat)) return;addStartMarker(lng, lat, "起点");
}// 更新终点(供 Qt 调用)
function updateEnd(loc){if(!loc) return;var parts = loc.split(',');if(parts.length !== 2) return;var lng = parseFloat(parts[0]);var lat = parseFloat(parts[1]);if(isNaN(lng) || isNaN(lat)) return;addEndMarker(lng, lat, "终点");
}// 绘制路线(直线示意)
function drawRoute(startLoc, endLoc){if(!startLoc || !endLoc) return;var s = startLoc.split(',');var e = endLoc.split(',');if(s.length !== 2 || e.length !== 2) return;var start = [parseFloat(s[0]), parseFloat(s[1])];var end = [parseFloat(e[0]), parseFloat(e[1])];if(start.some(isNaN) || end.some(isNaN)) return;// 移除之前的路线if(routeLine) map.remove(routeLine);// 确保标记存在addStartMarker(start[0], start[1], "起点");addEndMarker(end[0], end[1], "终点");// 绘制直线路线routeLine = new AMap.Polyline({path: [start, end],strokeColor: "#FF0000",strokeWeight: 3});map.add(routeLine);// 自动调整视野map.setFitView([startMarker, endMarker]);
}
</script>
</body>
</html>
7.天气
我这里也是调用的高德api,其实可以换一个好一点的,它就是发送请求获得数据,我们分析json文本,自己设计格式
直接放源码了,有注释
weather::weather(QWidget *parent) :QWidget(parent),ui(new Ui::weather)
{ui->setupUi(this);// 背景this->setStyleSheet("background-color: #787FBF;");// 对象初始化mapView = new QWebEngineView(this);manager = new QNetworkAccessManager(this);QString htmlPath = QCoreApplication::applicationDirPath() + "/weather/weather.html";mapView->load(QUrl::fromLocalFile(htmlPath));mapView->setGeometry(0, 59, 1071, 471);mapView->show();// 直接加载默认城市(杭州)天气getWeatherByCity("杭州");
}weather::~weather()
{delete ui;
}// 获取城市 adcode
void weather::getWeatherByCity(const QString &cityName)
{QUrl url("https://restapi.amap.com/v3/geocode/geo");QUrlQuery query;query.addQueryItem("key", WEA_KEY);query.addQueryItem("address", cityName);url.setQuery(query);QNetworkReply *reply = manager->get(QNetworkRequest(url));connect(reply, &QNetworkReply::finished, this, [=]() {QByteArray data = reply->readAll();reply->deleteLater();QJsonDocument doc = QJsonDocument::fromJson(data);QJsonArray geocodes = doc.object()["geocodes"].toArray();if(!geocodes.isEmpty()) {QString adcode = geocodes[0].toObject()["adcode"].toString();if (!adcode.isEmpty()) {fetchWeather(adcode);}}});
}// 获取天气信息
void weather::fetchWeather(const QString &adcode)
{QUrl url("https://restapi.amap.com/v3/weather/weatherInfo");QUrlQuery query;query.addQueryItem("key", WEA_KEY);query.addQueryItem("city", adcode);query.addQueryItem("extensions", "all");url.setQuery(query);QNetworkReply *reply = manager->get(QNetworkRequest(url));connect(reply, &QNetworkReply::finished, this, [=]() {QByteArray data = reply->readAll();reply->deleteLater();QString jsonStr = QString::fromUtf8(data);// 转义单引号jsonStr.replace("'", "\\'");QString jsCode = QString("updateWeatherData('%1')").arg(jsonStr);mapView->page()->runJavaScript(jsCode);});
}// 点击搜索按钮
void weather::on_search_button_clicked()
{QString cityName = ui->lineEdit->text().trimmed();if (!cityName.isEmpty()) {getWeatherByCity(cityName);}
}
html源码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>天气预报</title>
<style>body {font-family: "微软雅黑", sans-serif;background-color: #787FBF;color: #fff;margin: 0;padding: 10px;}h1, h2 {margin: 0 0 10px 0;}#current {margin-bottom: 20px;font-size: 16px;}#forecast {border-collapse: collapse;width: 100%;font-size: 14px;}#forecast th, #forecast td {border: 1px solid #fff;padding: 5px 10px;text-align: left;}#forecast th {background-color: rgba(255,255,255,0.2);}
</style>
</head>
<body><h1 id="city">城市</h1>
<div id="current"><p>温度:<span id="temp">--</span>°C</p><p>天气:<span id="weather">--</span></p>
</div><h2>未来预报:</h2>
<table id="forecast"><thead><tr><th>日期</th><th>白天 / 夜晚天气</th><th>温度</th></tr></thead><tbody><!-- JS 动态生成内容 --></tbody>
</table><script>
function updateWeatherData(jsonStr) {try {let data = JSON.parse(jsonStr);// 城市let city = data["city"] || (data["forecasts"] && data["forecasts"][0]["city"]) || "未知";document.getElementById("city").innerText = city;// 当前温度/天气let weatherNow = (data["lives"] && data["lives"].length>0) ? data["lives"][0] : null;let temp = "--", weather = "--";if(weatherNow) {temp = weatherNow["temperature"] || "--";weather = weatherNow["weather"] || "--";} else if(data["forecasts"] && data["forecasts"].length>0) {let cast = data["forecasts"][0]["casts"][0];if(cast) {temp = cast["daytemp"] || "--";weather = cast["dayweather"] || "--";}}document.getElementById("temp").innerText = temp;document.getElementById("weather").innerText = weather;// 未来天气表格let tbody = document.getElementById("forecast").getElementsByTagName("tbody")[0];tbody.innerHTML = "";let forecasts = data["forecasts"] || [];forecasts.forEach(f => {f["casts"].forEach(cast => {let tr = document.createElement("tr");tr.innerHTML = `<td>${cast["date"] || "--"} (${cast["week"] || "--"})</td><td>白天 ${cast["dayweather"] || "--"}, 夜晚 ${cast["nightweather"] || "--"}</td><td>${cast["daytemp"] || "--"}~${cast["nighttemp"] || "--"}°C</td>`;tbody.appendChild(tr);});});} catch(e) {console.error("解析天气 JSON 出错:", e);}
}
</script></body>
</html>
总结
项目主要功能就是这些,地图和天气部分有点难以理解,后续我会发布一篇关于qt和api搭配的博客,感谢你的浏览