一、运行效果图
1.1 桌面版(Fusion 风格)
- 核心布局:
- 顶部菜单栏(File/Help)与工具栏(Open 按钮);
- 中心区域显示图片,支持拖拽缩放与右键菜单;
- 深灰色背景衬托图片,符合桌面应用视觉规范。
1.2 安卓版(Material 风格)
- 移动适配:
- 左侧抽屉式导航(Drawer)替代传统菜单;
- 顶部工具栏包含汉堡菜单按钮与标题标签;
- 橙色主题色符合 Material Design 规范,适配触摸交互。
二、桌面版:经典桌面应用架构
2.1 ApplicationWindow 核心结构
ApplicationWindow
由四个主要区域组成,如下所示。菜单栏、工具栏和状态栏通常由 MenuBar
、ToolBar
或 TabBar
控件的实例填充,而内容区域是窗口的子项所在的位置。请注意,图像查看器应用程序没有状态栏;这就是为什么这里的代码显示以及上图中都没有它的原因。
然后,我们通过添加 Image
元素作为内容,开始在 main.qml
中构建用户界面。当用户打开图像时,此元素将保存图像,因此目前它只是一个占位符。background
属性用于为窗口提供一个元素,以放置在内容后面。当没有加载图像时,将显示此图像,如果纵横比不允许图像填充窗口的内容区域,则会显示图像周围的边框。
// 图片显示组件
Image {
id: image
anchors.fill: parent // 充满父窗口
fillMode: Image.PreserveAspectFit // 保持宽高比显示
asynchronous: true // 启用异步加载(避免界面冻结)
}
2.2 界面布局:从菜单栏到内容区域
2.2.1 工具栏(ToolBar)设计
我们添加 ToolBar
。这是使用窗口的 toolBar
属性完成的。在工具栏内,我们添加了一个 Flow
元素,该元素将允许内容在溢出到新行之前填充控件的宽度。在流中,我们放置了一个 ToolButton
。
在 ToolButton
的 onClicked
信号处理程序中是最后一段代码。它调用 fileOpenDialog
元素上的 open
方法。
// 工具栏配置
header: ToolBar {
Flow { // 流式布局容器
anchors.fill: parent
ToolButton {
text: qsTr("Open")
icon.name: "document-open"
onClicked: window.openFileDialog() // 点击触发文件对话框
}
}
}
2.2.2 显示图片
onAccepted
信号处理程序,其中保存窗口内容的 Image
元素被设置为显示所选文件。还有一个 onRejected
信号,但我们不需要在图像查看器应用程序中处理它。
// 平台文件对话框(桌面端专用)
Platform.FileDialog {
id: fileOpenDialog
title: "Select an image file" // 对话框标题
folder: Platform.StandardPaths.writableLocation(Platform.StandardPaths.DocumentsLocation) // 默认打开文档目录
nameFilters: [ "Image files (*.png *.jpeg *.jpg)" ] // 文件类型过滤
onAccepted: image.source = fileOpenDialog.file; // 文件选择确认时加载图片
}
注意:
fileOpenDialog
元素是Qt.labs.platform
模块中的FileDialog
控件。- 文件对话框可用于打开或保存文件。我们将
Qt.labs.platform
导入为Platform
,以避免与QtQuick.Controls
导入发生命名冲突,因此我们将其称为Platform.FileDialog
。
2.2.2 菜单栏(MenuBar)实现
我们继续使用 MenuBar
。要创建菜单,请将 Menu
元素放在菜单栏内,然后用 MenuItem
元素填充每个 Menu
。
在下面的代码中,我们创建两个菜单:File 和 Help。在 File (文件) 下,我们将 Open (打开) 置于与工具栏中的工具按钮相同的图标和作。在 Help (帮助) 下,您可以找到 About (关于),这将触发对 aboutDialog
元素的 open
方法的调用。
请注意,Menu
的 title
属性中的 & 符号 (“&”) 和 MenuItem
的 text
属性将以下字符转换为键盘快捷方式;例如,按 Alt+F,然后按 Alt+O 来触发打开的项目,从而进入文件菜单。
// 菜单栏配置
menuBar: MenuBar {
// 文件菜单
Menu {
title: qsTr("&File") // &表示快捷键(Alt+F)
MenuItem {
text: qsTr("&Open...")
icon.name: "document-open" // 使用系统图标
onTriggered: window.openFileDialog() // 触发文件对话框
}
}
// 帮助菜单
Menu {
title: qsTr("&Help")
MenuItem {
text: qsTr("&About...")
onTriggered: window.openAboutDialog() // 触发关于对话框
}
}
}
2.2.3 关于对话框
aboutDialog
元素基于 QtQuick.Controls
模块中的 Dialog
控件,该模块是自定义对话框的基础。我们将要创建的对话框如下图所示。
aboutDialog
的代码可以分为三个部分。首先,我们使用标题设置对话框窗口。然后,我们为对话框提供一些内容 – 在本例中为 Label
控件。最后,我们选择使用标准的 Ok 按钮来关闭对话框。
// 关于对话框
Dialog {
id: aboutDialog
title: qsTr("About") // 国际化标题
standardButtons: Dialog.Ok // 仅包含确定按钮
Label {
anchors.fill: parent
text: qsTr("QML Image Viewer\nA part of the QmlBook\nhttp://qmlbook.org") // 多行文本
horizontalAlignment: Text.AlignHCenter // 水平居中
}
}
三、安卓适配:移动交互范式转换
3.1 界面重构:从菜单到抽屉导航
与桌面应用程序相比,用户界面在移动设备上的外观和行为存在许多差异。我们的应用程序最大的区别在于访问作的方式。我们将使用一个抽屉,而不是菜单栏和工具栏,用户可以从中选择作。抽屉可以从侧面滑入,但我们在标题中也提供了一个汉堡按钮。抽屉打开后的结果应用程序如下所示。
// 侧滑抽屉导航
Drawer {
id: drawer
width: Math.min(window.width, window.height) / 3 * 2 // 动态计算宽度(屏幕宽高的2/3)
height: window.height
edge: Qt.LeftEdge // 从左侧滑出
// 导航列表视图
ListView {
focus: true
currentIndex: -1 // 初始无选中项
anchors.fill: parent
// 列表项代理
delegate: ItemDelegate {
width: parent.width
text: model.text // 显示模型文本
highlighted: ListView.isCurrentItem
onClicked: {
drawer.close() // 点击后关闭抽屉
model.triggered() // 执行模型定义的操作
}
}
// 导航数据模型
model: ListModel {
ListElement {
text: qsTr("Open...")
triggered: function(){ window.openFileDialog(); }
}
ListElement {
text: qsTr("About...")
triggered: function(){ window.openAboutDialog(); }
}
}
ScrollIndicator.vertical: ScrollIndicator { } // 垂直滚动条
}
}
代码详解:
Drawer
组件作为子组件添加到ApplicationWindow
中。- 在抽屉中,我们放置了一个包含
ItemDelegate
实例的ListView
。它还包含一个ScrollIndicator
,用于显示长列表的哪一部分。由于我们的列表仅包含两个项目,因此该指标在此示例中不可见。 - 抽屉的
ListView
是从ListModel
填充的,其中每个ListItem
对应于一个菜单项。每次单击一个项目时,在onClicked
方法中,都会调用相应ListItem
的triggered
方法。这样,我们就可以使用单个委托来触发不同的作。
3.2 ToolBar改造
这里我们不使用桌面样式的工具栏,重新添加了一个用于打开抽屉的按钮和一个用于应用程序标题的标签。
// Material风格工具栏
header: ToolBar {
Material.background: Material.Orange // 使用Material橙色主题
// 菜单按钮
ToolButton {
id: menuButton
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
icon.source: "images/baseline-menu-24px.svg" // 使用矢量图标
onClicked: drawer.open() // 点击打开侧滑菜单
}
// 标题标签
Label {
id: titleLabel
anchors.centerIn: parent
text: "Image Viewer"
font.pixelSize: 20 // 字号设置
elide: Label.ElideRight // 文本过长时右侧省略
}
}
通过上面的更改,我们已将桌面图像查看器转换为适合移动设备的版本。
四、公共代码
查看上面实现的代码,我们可以看到桌面和移动端大部分代码是相同的。相同的部分大多与应用程序的文档(即图像)相关联,这些变化分别考虑了桌面和移动设备的不同交互模式。
4.1 文件选择器
对于上面说的相同的代码,我们希望创建统一的代码库。QML 通过使用文件选择器来支持这一点。
文件选择器的工作原理是,如果存在选择器,则用替代文件替换文件。
通过在与要替换的文件相同的目录中创建名为 +selector
的目录(其中 selector
表示选择器的名称),可以将与要替换的文件同名的文件放置在该目录中。当选择器存在时,将选取目录中的文件,而不是原始文件。
选择器基于平台:例如 android、ios、osx、linux、qnx 等。它们还可以包括所使用的 Linux 发行版的名称(如果已标识),例如 debian、ubuntu、fedora。最后,它们还包括 locale,例如 en_US、sv_SE 等。
4.2 ImageViewerWindow组件
先来提取公共代码为一个组件,为此,我们创建了 ImageViewerWindow
元素,该元素将用于两个变体,而不是 ApplicationWindow
。这将由对话框、Image
元素和背景组成。为了使对话框的 open 方法可用于特定于平台的代码,我们需要通过函数 openFileDialog
和 openAboutDialog
公开它们。
// ImageViewerWindow.qml
import QtQuick
import QtQuick.Controls
import Qt.labs.platform as Platform // 使用实验室平台的组件(文件对话框)
ApplicationWindow {
// 窗口功能函数定义
function openFileDialog() { fileOpenDialog.open(); } // 打开文件选择对话框
function openAboutDialog() { aboutDialog.open(); } // 打开关于对话框
visible: true // 窗口可见性
title: qsTr("Image Viewer") // 国际化窗口标题
// 窗口背景设置
background: Rectangle {
color: "darkGray" // 深灰色背景
}
// 图片显示组件
Image {
id: image
anchors.fill: parent // 充满父窗口
fillMode: Image.PreserveAspectFit // 保持宽高比显示
asynchronous: true // 启用异步加载(避免界面冻结)
}
// 平台文件对话框(桌面端专用)
Platform.FileDialog {
id: fileOpenDialog
title: "Select an image file" // 对话框标题
folder: Platform.StandardPaths.writableLocation(Platform.StandardPaths.DocumentsLocation) // 默认打开文档目录
nameFilters: [ "Image files (*.png *.jpeg *.jpg)" ] // 文件类型过滤
onAccepted: image.source = fileOpenDialog.file; // 文件选择确认时加载图片
}
// 关于对话框
Dialog {
id: aboutDialog
title: qsTr("About") // 国际化标题
standardButtons: Dialog.Ok // 仅包含确定按钮
Label {
anchors.fill: parent
text: qsTr("QML Image Viewer\nA part of the QmlBook\nhttp://qmlbook.org") // 多行文本
horizontalAlignment: Text.AlignHCenter // 水平居中
}
}
}
4.3 桌面版和安卓版适配
我们将用户界面基于 ImageViewerWindow
而不是 ApplicationWindow
。然后我们向其添加特定于平台的部分,例如 MenuBar
和 ToolBar
。唯一的变化是,打开相应对话框的调用是针对新功能进行的,而不是直接针对对话框控件进行的。
我们为默认样式 Fusion(即用户界面的桌面版本)创建一个新的 main.qml
// ================================================
// desktop (win) main.qml - 桌面端主界面
// ================================================
import QtQuick
import QtQuick.Controls
ImageViewerWindow {
id: window
width: 640 // 初始宽度
height: 480 // 初始高度
// 菜单栏配置
menuBar: MenuBar {
// 文件菜单
Menu {
title: qsTr("&File") // &表示快捷键(Alt+F)
MenuItem {
text: qsTr("&Open...")
icon.name: "document-open" // 使用系统图标
onTriggered: window.openFileDialog() // 触发文件对话框
}
}
// 帮助菜单
Menu {
title: qsTr("&Help")
MenuItem {
text: qsTr("&About...")
onTriggered: window.openAboutDialog() // 触发关于对话框
}
}
}
// 工具栏配置
header: ToolBar {
Flow { // 流式布局容器
anchors.fill: parent
ToolButton {
text: qsTr("Open")
icon.name: "document-open"
onClicked: window.openFileDialog() // 点击触发文件对话框
}
}
}
}
接下来,我们必须创建一个特定于移动设备的 main.qml
。这将基于 Material 主题。在这里,我们保留了 Drawer
和特定于移动设备的工具栏。同样,唯一的变化是对话框的打开方式。
// ================================================
// android main.qml - 移动端主界面(Material设计)
// ================================================
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Material 2.1 // Material设计库
ImageViewerWindow {
id: window
width: 360 // 移动端适配宽度
height: 520 // 移动端适配高度
// 侧滑抽屉导航
Drawer {
id: drawer
width: Math.min(window.width, window.height) / 3 * 2 // 动态计算宽度(屏幕宽高的2/3)
height: window.height
edge: Qt.LeftEdge // 从左侧滑出
// 导航列表视图
ListView {
focus: true
currentIndex: -1 // 初始无选中项
anchors.fill: parent
// 列表项代理
delegate: ItemDelegate {
width: parent.width
text: model.text // 显示模型文本
highlighted: ListView.isCurrentItem
onClicked: {
drawer.close() // 点击后关闭抽屉
model.triggered() // 执行模型定义的操作
}
}
// 导航数据模型
model: ListModel {
ListElement {
text: qsTr("Open...")
triggered: function(){ window.openFileDialog(); }
}
ListElement {
text: qsTr("About...")
triggered: function(){ window.openAboutDialog(); }
}
}
ScrollIndicator.vertical: ScrollIndicator { } // 垂直滚动条
}
}
// Material风格工具栏
header: ToolBar {
Material.background: Material.Orange // 使用Material橙色主题
// 菜单按钮
ToolButton {
id: menuButton
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
icon.source: "images/baseline-menu-24px.svg" // 使用矢量图标
onClicked: drawer.open() // 点击打开侧滑菜单
}
// 标题标签
Label {
id: titleLabel
anchors.centerIn: parent
text: "Image Viewer"
font.pixelSize: 20 // 字号设置
elide: Label.ElideRight // 文本过长时右侧省略
}
}
}
总结
通过本文的介绍,我们学习了如何使用 Qt6 和 QML 创建一个简单的图像查看器,并适配桌面和安卓平台。我们了解了ApplicationWindow
、ToolBar
、MenuBar
、Dialog
等常用 UI 组件的使用方法,以及如何使用文件选择器和原生对话框来提高应用程序的兼容性和用户体验。希望本文能对你的 Qt 开发之旅有所帮助。