本页

照片表面

这是一个用于触摸设备的 QML 应用程序,它使用带有FolderListModel 的 Repeater 来访问文件夹中的内容,并使用PinchHandler 来处理获取内容上的捏合手势。

照片集

Photo Surface演示了如何使用带有FolderListModelFolderDialogRepeater 访问用户所选文件夹中的图片。该示例还展示了如何使用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()
            }

用户也可单击文件夹对话框图标打开它。在这里,一个图像QML 类型会显示该图标。在图像类型中,带有onTapped 信号处理器的TapHandler 调用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 {
                id: photoFrame
                required property date fileModified
                required property string fileName
                required property url fileUrl
                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
                }

处理拖动和捏合手势以及鼠标

每个相框中的DragHandlerPinchHandler 可处理拖动、缩放和旋转:

                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.110 之间缩放。捏合手势在触摸屏或多点触控触摸板上同样有效。变换框架可变换其内容(图像)。

DragHandler.target 属性也是隐式设置,因此您可以用一根手指在触摸屏或触摸板上拖动照片,也可以用鼠标拖动照片。在DragHandleronActiveChanged 信号处理程序中,选定的相框会通过增加其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
                }

源文件

示例项目 @ code.qt.io

另请参阅 QML 应用程序Qt Quick 示例与教程

© 2026 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.