QML SwipeView:实现一个简单的图片浏览器

0 阅读4分钟

这篇用 SwipeView 做一个图片浏览器:四张壁纸左右滑动翻页,支持手动播放和自动播放。自动播放时中间的按钮图标会从播放变成暂停,点一下停住,再点继续,交互状态一目了然。

  • SwipeView 加载大图Image 自适应、懒加载只建相邻页
  • 自动播放与暂停Timer 驱动轮播,图标随状态切换
  • 循环翻页 — 循环滚动图片,按钮手动翻页与定时翻页共用同一套逻辑

页面结构:SwipeView 装图片

5.gif

图片浏览器的主体是占满内容区的 SwipeView,四张壁纸作为四个页面。图片资源放在工程的 wallpaper 目录并通过 res.qrc 打进 qrc:/,代码里用资源路径引用。

演示代码

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

FadeInAnimation {
    id: root

    function nextPhoto() {
        if (swipeView.currentIndex >= swipeView.count - 1) {
            swipeView.currentIndex = 0
        } else {
            swipeView.currentIndex++;
        }
    }

    function prevPhoto() {
        if (swipeView.currentIndex <= 0) {
            swipeView.currentIndex = swipeView.count - 1
        } else {
            swipeView.currentIndex--;
        }
    }

    function playAlbum() {
        timer.running = !timer.running
    }

    Timer {
        id: timer
        interval: 1000
        repeat: true
        running: false
        onTriggered: { root.nextPhoto() }
    }

    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 20
        spacing: 15

        // ... 省略标题组件 TitleSeparator ...

        SwipeView {
            id: swipeView
            Layout.fillWidth: true
            Layout.fillHeight: true
            clip: true

            Repeater {
                model: [
                    "qrc:/wallpaper/1.png",
                    "qrc:/wallpaper/2.png",
                    "qrc:/wallpaper/3.png",
                    "qrc:/wallpaper/4.png"
                ]
                Loader {
                    active: SwipeView.isCurrentItem || SwipeView.isNextItem || SwipeView.isPreviousItem
                    sourceComponent: Image {
                        source: modelData
                        anchors.fill: parent
                        fillMode: Image.PreserveAspectFit
                    }
                }
            }
        }

        Rectangle {
            Layout.fillWidth: true
            Layout.preferredHeight: 60
            border.width: 1
            border.color: "#ccc"
            radius: 6

            Row {
                anchors.centerIn: parent
                spacing: 10

                RoundButton {
                    width: 40
                    height: 40
                    icon.source: "qrc:/images/left.svg"
                    icon.width: 40
                    icon.height: 40
                    ToolTip.visible: hovered
                    ToolTip.text: "上一个"
                    onClicked: root.prevPhoto()
                }

                // 播放/暂停共用一个按钮,图标随自动播放状态切换
                RoundButton {
                    width: 40
                    height: 40
                    icon.source: timer.running ? "qrc:/images/pause.svg" : "qrc:/images/play.svg"
                    icon.width: 40
                    icon.height: 40
                    ToolTip.visible: hovered
                    ToolTip.text: timer.running ? "暂停播放" : "自动播放"
                    onClicked: root.playAlbum()
                }

                RoundButton {
                    width: 40
                    height: 40
                    icon.source: "qrc:/images/right.svg"
                    icon.width: 40
                    icon.height: 40
                    ToolTip.visible: hovered
                    ToolTip.text: "下一个"
                    onClicked: root.nextPhoto()
                }
            }
        }
    }
}

关键逻辑解析

页面模型直接放图片路径数组Repeatermodel 是四个 qrc:/wallpaper/*.png 字符串,delegate 里用 modelData 取出当前项当图片地址,一个 Image 就渲染出了四页。比逐个手写 Image 干净,加第五张图只需往数组里加一行。

图片要用 anchors.fill + PreserveAspectFit 适配。壁纸是 1060×596 的横屏大图,而页面区域小得多。不给 Image 设置布局它会按隐式尺寸显示左上角一块;anchors.fill: parent 让图片占满整页,fillMode: Image.PreserveAspectFit 则等比缩放、完整显示、四周留白,既不裁图也不变形。

Loader 三条件懒加载在这里同样适用。大图解码费内存,四张一起建不如只建当前页和左右相邻页。SwipeView.isCurrentItem / isNextItem / isPreviousItem 保证滑动动画始终有图可画,滑远的页面自动销毁释放内存。

控制逻辑:翻页、轮播与暂停

页面本身只有滑动能力,上一张/下一张/自动播放全靠底下三个按钮 + 一个 Timer 驱动 currentIndex 实现。

关键逻辑解析

手动翻页是"边界回绕"的算术nextPhoto:已在最后一页就跳回 0,否则 currentIndex++prevPhoto 对称:已在第一页就跳到最后一张。四张图循环浏览,怎么滑都不会越界。按钮的 onClicked 直接调用这两个函数即可。

自动播放是 Timer 周期调用 nextPhotoTimer 每 1000ms 触发一次 onTriggered,内部调用 root.nextPhoto()——轮播本质就是"定时翻下一页"。repeat: true 让它一直跑,running 控制启停。

播放/暂停共用一个按钮,靠 timer.running 切图标和提示。中间按钮的 icon.source 写成 timer.running ? "pause.svg" : "play.svg",跑着显示暂停图标、停着显示播放图标;ToolTip.text 同理在「暂停播放 / 自动播放」间切换。用户悬停能看到下一步动作,点按结果和图标一致,不需要第二个"状态灯"。

playAlbum 只做一件事:翻转 runningtimer.running = !timer.running,播放中点了变暂停,暂停中点了继续。没有多余状态变量,running 本身就是唯一状态源,所有 UI 都从它派生。

整体布局:图片区与底部控制条

去掉指示器后,界面只剩两个区域,都由 ColumnLayout 从上往下排:SwipeView 图片区占满剩余空间(Layout.fillHeight: true),底部的控制条是一个带圆角边框的 Rectangle,固定 preferredHeight: 60,三个 RoundButtonRow 居中排布。要不要"当前第几张"的提示?这个 demo 选择了不加——四张图循环浏览,回绕行为让"在哪一张"变得不那么关键,把指示器留给需要精确定位的场景(如第一篇的自定义指示器 demo),图片浏览器保持纯粹的两段式结构。

运行验证

  1. Qt Creator 打开 qml_swipeview/CMakeLists.txt,按 Ctrl+R 运行;
  2. 左侧点「图片浏览器(简易版)」,拖拽图片左右滑动,观察图片自适应缩放、四周留白;
  3. 点右侧「下一个」到底后继续点,看是否回绕到第一张;点左侧「上一个」同理;
  4. 点中间播放按钮,图片每 1 秒自动切换,图标变暂停;再点一下停止轮播,图标恢复播放,悬停查看 ToolTip 文案变化。

扩展复用方向

  • 图片路径数组换成 ListModel,就能支持运行时增删照片、显示文件名等信息;
  • Timer.interval 做成滑块可调,就是"播放速度"设置项;
  • 想加"点击图片看大图"或双击放大,在 Image 外包一层 MouseArea 即可,轮播逻辑不受影响。

已验证环境