照片表面
这是一款用于触摸设备的 QML 应用程序,它使用带有FolderListModel 的 Repeater 来访问文件夹中的内容,并使用PinchHandler 来处理获取内容上的捏合手势。
Photo Surface演示了如何使用带有FolderListModel 和FolderDialog 的Repeater 访问用户所选文件夹中的图片,以及如何使用Qt Quick 输入处理程序在同一项目中处理拖动、旋转和捏合缩放。
所有应用程序代码都包含在一个 QML 文件photosurface.qml
中。内联 JavaScript 代码用于在照片表面放置、旋转和缩放图片。
运行示例
要从 Qt Creator,打开Welcome 模式,然后从Examples 中选择示例。更多信息,请参阅Qt Creator: 教程:构建并运行。
创建主窗口
要创建 Photo Surface 应用程序的主窗口,我们使用Window QML 类型作为根项目。它会自动设置窗口,以便与 Qt Quick图形类型:
Window { id: root visible: true width: 1024; height: 600 color: "black" title: Application.displayName + " : " + folderModel.folder property real defaultSize: 200 property real surfaceViewportRatio: 1.5 property var imageNameFilters: ["*.png", "*.jpg", "*.gif"] // overridden in main.cpp
访问文件夹内容
我们使用Repeater QML 类型和FolderListModel 来显示文件夹中的 GIF、JPG 和 PNG 图像(尽管 main.cpp 可能会扩展支持的图像类型列表):
Repeater { model: FolderListModel { id: folderModel objectName: "folderModel" showDirs: false nameFilters: root.imageNameFilters }
要使用FolderListModel 类型,我们必须导入它:
import Qt.labs.folderlistmodel
我们使用 FolderDialog 让用户选择包含图片的文件夹:
FolderDialog { id: folderDialog title: qsTr("Choose a folder with some images") onAccepted: folderModel.folder = selectedFolder }
要使用 FolderDialog 类型,我们需要添加以下导入语句:
import QtQuick.Dialogs
我们使用folderDialog.open()
函数在初始幻灯片放映结束时打开文件对话框,除非在命令行参数中指定了文件夹路径:
Component.onDestruction: { folderIcon.visible = true const lastArg = Application.arguments.slice(-1)[0] const standardPicturesLocations = StandardPaths.standardLocations(StandardPaths.PicturesLocation) const hasValidPicturesLocation = standardPicturesLocations.length > 0 if (hasValidPicturesLocation) folderDialog.currentFolder = standardPicturesLocations[0] if (/.*hotosurface.*|--+/.test(lastArg)) { if (hasValidPicturesLocation) folderModel.folder = standardPicturesLocations[0] else folderDialog.open() }
用户也可以点击文件夹对话框图标打开它。我们使用ImageQML 类型来显示图标。在图像类型中,我们使用TapHandler 和onTapped
信号处理器来调用folderDialog.open()
函数:
Image { id: folderIcon visible: false anchors.top: parent.top anchors.left: parent.left anchors.margins: 10 source: "resources/folder.png" TapHandler { onTapped: folderDialog.open() } HoverHandler { id: folderMouse } ToolTip.visible: folderMouse.hovered ToolTip.text: qsTr(`Open an image directory (${openShortcut.nativeText})`) ToolTip.delay: 1000 Shortcut { id: openShortcut sequence: StandardKey.Open onActivated: folderDialog.open() } }
在照片表面显示图像
我们使用Rectangle 作为Repeater 的委托,为FolderListModel 在选定文件夹中找到的每张图片提供一个框架。我们使用 JavaScriptMath()
方法在照片表面随机放置边框,并以随机角度旋转边框和缩放图片。边框颜色表示交互状态:
delegate: Rectangle { required property date fileModified required property string fileName required property url fileUrl id: photoFrame objectName: "frame-" + fileName width: image.width * (1 + 0.10 * image.height / image.width) height: image.height * 1.10 scale: root.defaultSize / Math.max(image.sourceSize.width, image.sourceSize.height) border.color: pinchHandler.active || dragHandler.active ? "darkturquoise" : mouse.hovered ? "darkseagreen" : "saddlebrown" border.width: 3 / scale antialiasing: true Component.onCompleted: { x = Math.random() * root.width - width / 2 y = Math.random() * root.height - height / 2 rotation = Math.random() * 13 - 6 } Image { id: image anchors.centerIn: parent fillMode: Image.PreserveAspectFit source: photoFrame.fileUrl antialiasing: true }
处理拖动、捏合手势和鼠标
我们在每个相框中使用DragHandler 和PinchHandler 来处理拖动、缩放和旋转:
PinchHandler { id: pinchHandler minimumRotation: -360 maximumRotation: 360 minimumScale: 0.1 maximumScale: 10 grabPermissions: PointerHandler.CanTakeOverFromAnything // and never gonna give it up onActiveChanged: if (active) photoFrame.z = ++flick.highestZ } DragHandler { id: dragHandler onActiveChanged: { if (active) photoFrame.z = ++flick.highestZ else anim.restart(centroid.velocity) } }
由于PinchHandler 是在矩形内部声明的,因此PinchHandler.target
属性是隐式设置的,因此捏合手势可操作矩形。旋转属性指定相框可以旋转到所有角度,缩放属性指定相框可以在0.1
和10
之间缩放。捏合手势在触摸屏或多点触控触摸板上同样有效。变换框架可变换其内容(图像)。
DragHandler.target
属性也是隐式设置,因此您可以用一根手指在触摸屏或触摸板上拖动照片,也可以用鼠标拖动照片。在DragHandler 的onActiveChanged
信号处理程序中,我们通过增加z
属性的值,将选中的相框提升到顶部(而共享的highestZ
属性则保存迄今为止使用过的最大z
值)。拖动结束后,我们会开始播放一段动画,让相框沿同一方向移动一会儿,然后放慢速度并停止。如果将照片 "甩 "过曲面的边缘,曲面就会展开以容纳新的位置。您可以通过包含中继器及其填充的所有相框的ScrollView 移动查看曲面的不同部分。
由于您可以通过 DragHandlers 用两根手指拖动两张照片,也可以用两根手指捏一张PinchHandler ,而照片集往往会堆叠在一起,因此我们需要调整grabPermissions
,使PinchHandler 具有优先权:当捏的手势开始时,它不允许 DragHandlers 再次接管触摸点抓取。
为了使示例在没有触摸设备的电脑上更具交互性,我们添加了上述 border.color 所依赖的HoverHandler 以及两个WheelHandlers 。其中一个可以按住 Ctrl 键,使用鼠标滚轮围绕鼠标光标旋转照片;另一个可以按住 Shift 键,使用鼠标滚轮放大或缩小光标下的位置。这两种方法都能以与DragHandler 相同的方式提升照片:
HoverHandler { id: mouse } WheelHandler { acceptedModifiers: Qt.ControlModifier property: "rotation" onActiveChanged: if (active) photoFrame.z = ++flick.highestZ } WheelHandler { acceptedModifiers: Qt.ShiftModifier property: "scale" onActiveChanged: if (active) photoFrame.z = ++flick.highestZ }
另请参阅 QML 应用程序。
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.