En esta página

Superficie fotográfica

Aplicación QML para dispositivos táctiles que utiliza un repetidor con FolderListModel para acceder al contenido de una carpeta y PinchHandler para realizar gestos de pellizco sobre el contenido obtenido.

Una colección de fotos

Photo Surface muestra cómo utilizar un Repeater con un FolderListModel y un FolderDialog para acceder a imágenes de una carpeta seleccionada por un usuario. El ejemplo también muestra cómo manejar el arrastre, la rotación y el zoom de pellizco dentro del mismo elemento utilizando Qt Quick Input Handlers.

Todo el código de la aplicación está contenido en un archivo QML, photosurface.qml. El código JavaScript en línea coloca, rota y escala las imágenes en la superficie de la foto.

Ejecución del ejemplo

Para ejecutar el ejemplo desde Qt Creatorabra el modo Welcome y seleccione el ejemplo de Examples. Para más información, consulte Qt Creator: Tutorial: Construir y ejecutar.

Crear la ventana principal

Para crear la ventana principal de la aplicación Photo Surface, utiliza el tipo QML Window como elemento raíz. Automáticamente configura la ventana para su uso con Qt Quick tipos gráficos:

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

Acceder al contenido de la carpeta

Utiliza un tipo QML Repeater junto con el FolderListModel para mostrar las imágenes GIF, JPG y PNG localizadas en una carpeta (aunque main.cpp puede ampliar la lista de tipos de imagen soportados):

        Repeater {
            model: FolderListModel {
                id: folderModel
                objectName: "folderModel"
                showDirs: false
                nameFilters: root.imageNameFilters
            }

Importe el tipo FolderListModel:

import Qt.labs.folderlistmodel

Un FolderDialog permite a los usuarios seleccionar la carpeta que contiene las imágenes:

    FolderDialog {
        id: folderDialog
        title: qsTr("Choose a folder with some images")
        onAccepted: folderModel.folder = selectedFolder
    }

Para utilizar el tipo FolderDialog, añada la siguiente sentencia import:

import QtQuick.Dialogs

La función folderDialog.open() abre el diálogo de archivo cuando finaliza la presentación de diapositivas inicial, a menos que se haya proporcionado una ruta de carpeta como argumento de la línea de comandos:

        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()
            }

Los usuarios también pueden hacer clic en el icono de diálogo de carpeta para abrirlo. Aquí, un tipo QML Imagen muestra el icono. Dentro del tipo Image, un TapHandler con el manejador de señal onTapped llama a la función 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()
        }
    }

Visualización de imágenes en la superficie fotográfica

Un Rectangle se utiliza como delegado de un Repeater para proporcionar un marco para cada imagen que el FolderListModel encuentra en la carpeta seleccionada. Los métodos JavaScript Math() colocan los marcos aleatoriamente en la superficie de la foto, los giran en ángulos aleatorios y escalan las imágenes. El color del borde indica el estado de la interacción:

            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
                }

Manejo de los gestos de arrastrar y pellizcar, y del ratón

Un DragHandler y un PinchHandler en cada marco de foto manejan los gestos de arrastrar, pellizcar y rotar:

                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)
                    }
                }

Debido a que el PinchHandler se declara dentro del Rectángulo, la propiedad PinchHandler.target se establece implícitamente para que los gestos de pellizco manipulen el Rectángulo. Las propiedades de rotación especifican que los marcos se pueden rotar en todos los ángulos, y las propiedades de escala especifican que se pueden escalar entre 0.1 y 10. El gesto de pellizcar funciona igual de bien en una pantalla táctil o en un panel táctil multitáctil. Transformar el marco transforma su contenido (la Imagen).

La propiedad DragHandler.target también se establece implícitamente, de modo que se puede arrastrar una foto con un dedo en una pantalla táctil o touchpad, o con un ratón. En el manejador de señales DragHandler's onActiveChanged, el marco de la foto seleccionada se eleva a la parte superior aumentando el valor de su propiedad z (mientras que la propiedad compartida highestZ mantiene el mayor valor z que se haya utilizado hasta el momento). Cuando finaliza el arrastre, comienza una animación que lo mantiene en movimiento en la misma dirección durante un rato, ralentizándose y llegando a detenerse. Si "lanza" una foto más allá del borde de la superficie, ésta se expande para adaptarse a su nueva posición. Puedes desplazarte para ver distintas partes de la superficie a través de la página ScrollView que contiene el Repetidor y todos los marcos de fotos que rellena.

Como puede arrastrar dos fotos con dos dedos a través de sus DragHandlers, y también puede pellizcar una PinchHandler con dos dedos, y las colecciones de fotos tienden a apilarse unas sobre otras, necesita ajustar grabPermissions para que el PinchHandler tenga prioridad: cuando comienza el gesto de pellizcar, no permite que los DragHandlers vuelvan a hacerse cargo de los agarres de los puntos de contacto.

Para hacer el ejemplo más interactivo en ordenadores sin dispositivos táctiles, añade el HoverHandler del que depende el border.color anterior, y dos WheelHandlers. Uno te permite mantener pulsada la tecla Ctrl y utilizar la rueda del ratón para hacer girar la foto alrededor del cursor del ratón; con el otro, puedes mantener pulsada la tecla Mayús y utilizar la rueda del ratón para acercar o alejar la posición bajo el cursor. Ambas opciones también levantan la foto de la misma forma que lo hace la página DragHandler anterior:

                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
                }

Archivos fuente

Proyecto de ejemplo @ code.qt.io

Ver también Aplicaciones QML y Qt Quick Ejemplos y Tutoriales.

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