为QML程序添加启动Logo:提升用户体验
-
- 一、背景与问题分析
- 二、实现原理详解
- 三、效果
- 四、完整实现步骤
-
- 第一步:项目环境搭建
- 第二步:创建动态启动Logo
-
- [1、Logo生成脚本 `gen_startup_logo.py`](#1、Logo生成脚本
gen_startup_logo.py) - 2、运行脚本
- [1、Logo生成脚本 `gen_startup_logo.py`](#1、Logo生成脚本
- 第三步:编译与部署
-
- [1. 设置编译环境](#1. 设置编译环境)
- [2. 编译项目](#2. 编译项目)
- [3. 部署应用程序](#3. 部署应用程序)
- [4. 运行测试](#4. 运行测试)
一、背景与问题分析
1、为什么需要启动Logo?
在实际开发中,很多QML应用程序在启动时需要执行一些耗时的初始化操作,比如:
- 加载大型资源文件
- 初始化复杂的业务逻辑
- 连接数据库或网络服务
- 等待后端服务准备就绪
问题表现:用户点击应用后,屏幕会黑屏或空白10几秒钟,给用户带来"应用卡死"或"无响应"的负面体验。
解决方案:通过显示启动Logo界面,我们能够:
- 向用户反馈应用正在正常启动
- 展示品牌形象和视觉元素
- 提供进度反馈,降低用户的等待焦虑
- 提升应用的专业度和用户体验
二、实现原理详解
1、技术思路
我们的解决方案基于以下关键点:
- 双窗口架构:启动窗口 + 主界面窗口
- 透明背景技术:实现自定义形状的启动界面
- 异步加载机制:在后台初始化时保持界面响应
- 动态切换技术:平滑过渡到主界面
2、窗口标志位说明
qml
flags: Qt.SplashScreen | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint
Qt.SplashScreen:将窗口标记为启动屏幕Qt.FramelessWindowHint:移除窗口边框和标题栏Qt.WindowStaysOnTopHint:确保启动界面始终在最前面
三、效果

四、完整实现步骤
第一步:项目环境搭建
复制张贴代码段的内容在PowerShell终端即可
1、主程序main.cpp
bash
@'
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include <QTimer>
#include <QtQml/QQmlEngine>
#include <QQmlContext>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
app.setApplicationName("MyApp");
app.setApplicationVersion("1.0.0");
app.setOrganizationName("MyCompany");
app.setOrganizationDomain("mycompany.com");
QQmlApplicationEngine engine;
// 模拟一些耗时的初始化工作
QTimer::singleShot(1000, [&engine]() {
QMetaObject::invokeMethod(engine.rootObjects().first(), "showStage",Qt::DirectConnection,QVariant(0.2),QVariant("进度1"));
QTimer::singleShot(2000, [&engine]() {
QMetaObject::invokeMethod(engine.rootObjects().first(), "showStage",Qt::DirectConnection,QVariant(0.4),QVariant("进度2"));
QTimer::singleShot(1000, [&engine]() {
QMetaObject::invokeMethod(engine.rootObjects().first(), "showStage",Qt::DirectConnection,QVariant(0.8),QVariant("进度3"));
QTimer::singleShot(1000, [&engine]() {
QMetaObject::invokeMethod(engine.rootObjects().first(), "showMainPage");
});
});
});
});
// 加载启动界面
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
'@ | Out-File -FilePath main.cpp -Encoding utf8
代码解释:
- 使用
QTimer::singleShot模拟异步初始化过程 - 通过
QMetaObject::invokeMethod调用QML中的函数更新进度 - 分阶段更新进度,让用户了解启动状态
2、主QML文件main.qml
bash
@'
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
id: mainWindow
width: 800
height: 600
visible: true
flags: Qt.SplashScreen | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint // 无边框,类似启动画面
color: "transparent" // 透明背景
// 启动 Logo 界面
Rectangle {
id: startupScreen
anchors.fill: parent
color: "transparent" // 深蓝色背景
z: 10 // 确保在最上层
// GIF 启动动画
AnimatedImage {
id: startupLogo
source: "startup-logo.gif"
width: 300
height: 300
anchors.centerIn: parent
playing: true
// Logo 背景(可选,根据需要添加)
Rectangle {
id: logoBackground
anchors.fill: parent
color: "transparent" // 透明或纯色背景
radius: 20 // 圆角(可选)
z: -1 // 在 Logo 后面
}
}
// 加载提示
Text {
id: loadingText
text: qsTr("正在加载...")
color: "#bdc3c7"
font.pixelSize: 25
anchors {
top: startupLogo.bottom
horizontalCenter: startupLogo.horizontalCenter
topMargin: 10
}
}
// 进度指示器
ProgressBar {
id: loadingProgress
width: 300
anchors {
top: loadingText.bottom
horizontalCenter: loadingText.horizontalCenter
topMargin: 20
}
from: 0.0
to: 100.0
value: 0.0
indeterminate: false
visible: true
}
}
// 主界面容器 - 初始隐藏
Loader {
id: mainInterfaceLoader
active: false
source: "MainPage.qml"
onLoaded: {
// 主界面加载完成后,调整窗口属性
mainWindow.flags = Qt.Window | Qt.WindowStaysOnTopHint // 恢复正常窗口样式
mainWindow.color = "#f0f0f0" // 设置正常背景色
mainWindow.visible=false
}
}
function showStage(percent,name) {
loadingProgress.value=percent*100
loadingText.text=name
}
// 显示主界面的函数
function showMainPage() {
console.log("初始化完成,切换到主界面")
startupLogo.visible = false
mainInterfaceLoader.active = true
}
}
'@ | Out-File -FilePath main.qml -Encoding utf8
3、主界面MainPage.qml
bash
@'
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
Window {
id: mainInterface
width: 800
height: 600
visible: true
title: qsTr("我的应用")
// 简单的主界面内容
Rectangle {
anchors.fill: parent
color: "#ffffff"
ColumnLayout {
anchors.centerIn: parent
spacing: 20
Text {
text: qsTr("欢迎使用")
font.pixelSize: 32
font.bold: true
Layout.alignment: Qt.AlignHCenter
}
Button {
text: qsTr("开始")
Layout.alignment: Qt.AlignHCenter
onClicked: {
console.log("应用已启动")
}
}
}
}
}
'@ | Out-File -FilePath MainPage.qml -Encoding utf8
4、项目配置文件QmlLogo.pro
bash
$content = @'
QT += quick quickcontrols2
QT += qml
SOURCES += main.cpp
resources.files = main.qml MainPage.qml startup-logo.gif
resources.prefix = /
RESOURCES += resources
DISTFILES += MainPage.qml
'@
$utf8NoBom = New-Object System.Text.UTF8Encoding $false
$writer = New-Object System.IO.StreamWriter("QmlLogo.pro", $false, $utf8NoBom)
$writer.Write($content)
$writer.Close()
第二步:创建动态启动Logo
1、Logo生成脚本 gen_startup_logo.py
bash
@'
from PIL import Image, ImageEnhance
import requests
from io import BytesIO
import math
import os
def download_image(url, save_path=None):
try:
response = requests.get(url)
response.raise_for_status()
image = Image.open(BytesIO(response.content)).resize((200,200), Image.Resampling.BILINEAR)
if save_path:
image.save(save_path)
return image
except Exception as e:
print(f"{e}")
return None
def create_blinking_gif(image, output_gif_path, blink_count=16, duration=80,
min_brightness=0.2, max_brightness=1.0, blink_pattern="sine"):
try:
if image.mode != 'RGBA':
image = image.convert('RGBA')
frames = []
for i in range(blink_count):
if blink_pattern == "sine":
brightness_factor = min_brightness + (max_brightness - min_brightness) * (0.5 + 0.5 * math.sin(i * math.pi / 4))
elif blink_pattern == "square":
brightness_factor = max_brightness if i % 2 == 0 else min_brightness
elif blink_pattern == "triangle":
phase = (i % 8) / 4.0
brightness_factor = min_brightness + (max_brightness - min_brightness) * (1 - abs(phase - 1))
else:
brightness_factor = min_brightness + (max_brightness - min_brightness) * (0.5 + 0.5 * math.sin(i * math.pi / 4))
enhancer = ImageEnhance.Brightness(image)
frame = enhancer.enhance(brightness_factor)
frames.append(frame)
frames[0].save(
output_gif_path,
save_all=True,
append_images=frames[1:],
duration=duration,
loop=0,
optimize=True
)
print(f"{output_gif_path}")
return True
except Exception as e:
print(f"{e}")
return False
if __name__ == "__main__":
pikachu_url = "https://www.pngall.com/wp-content/uploads/5/Pikachu-PNG-Images.png"
original_image_path = "pikachu_original.png"
blinking_gif_path = "startup-logo.gif"
pikachu_image = download_image(pikachu_url, original_image_path)
if pikachu_image:
success = create_blinking_gif(
pikachu_image,
blinking_gif_path,
blink_count=16,
duration=80,
min_brightness=0.2,
max_brightness=1.0,
blink_pattern="sine")
if success:
if os.path.exists(blinking_gif_path):
file_size = os.path.getsize(blinking_gif_path) / 1024 # KB
print(f"{file_size:.2f} KB")
'@ | Out-File -FilePath gen_startup_logo.py -Encoding utf8
2、运行脚本
bash
python gen_startup_logo.py
第三步:编译与部署
1. 设置编译环境
打开 x64 Native Tools Command Prompt for VS 2022 (或其他对应版本):
切换到项目目录
2. 编译项目
bash
C:\Qt\6.9.2\msvc2022_64\bin\qmake.exe .
nmake
3. 部署应用程序
bash
cd release
# 使用windeployqt6自动拷贝依赖
C:\Qt\6.9.2\msvc2022_64\bin\windeployqt6.exe QmlLogo.exe
4. 运行测试
bash
QmlLogo.exe