QML SwipeView:手势翻页与自定义指示器

SwipeView 是 Qt Quick Controls 里做手势翻页的容器,内容横向排开,滑动一下切换到一页,配合 PageIndicator 提示组件能够显示当前在第几页。

这篇介绍2个最简单的demo, SwipeView 怎么用,再额外增加一个页面指示器。

  • SwipeView 基础用法 --- 三个页面直接放容器里,左右滑动切换
  • 自定义指示器 --- 重写指示器组件,构建特殊效果

SwipeView 基础用法

三个 CustomRect 页面从上到下摞在 SwipeView 里(实际是横向排开),鼠标拖拽或触屏滑动即可翻页,页面底部浮着三个小圆点显示当前位置。

演示代码

qml 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

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

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

        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: "transparent"

            SwipeView {
                id: swipeView
                anchors.fill: parent
                clip: true

                CustomRect { description: "This is CustomRect1" }
                CustomRect { description: "This is CustomRect2" }
                CustomRect { description: "This is CustomRect3" }
            }

            PageIndicator {
                id: indicator
                anchors.bottomMargin: 20
                anchors.bottom: swipeView.bottom
                anchors.horizontalCenter: parent.horizontalCenter

                count: swipeView.count
                currentIndex: swipeView.currentIndex
            }
        }
    }
}

CustomRect 是这个仓库里复用的占位组件,本质是一个 Rectangle,只在正中显示一行描述文字,用来假装"这是一个页面"。你自己写的时候放任何内容都行。

关键逻辑解析

SwipeView 的用法就是"往容器里放页面"。每个直接子项就是一页,容器自带滑动识别、切换动画和惯性滚动。页面数量由子项个数决定,不需要你声明"我有 3 页"。

PageIndicator 自己不知道页数和页码,全靠外部喂 。它是纯粹的"展示型"控件,必须显式绑两个属性:count: swipeView.count 告诉它几个点,currentIndex: swipeView.currentIndex 告诉它亮第几个。SwipeView 负责翻页,PageIndicator 只负责画点,二者通过 currentIndex 通信。

基础写法里指示器只能看不能点PageIndicator.interactive 默认是 false,点圆点不会跳页。想让它可点击,下一节会看到 interactive: true 这行开关。

clip: true 记得加。滑动过程中相邻页面会短暂超出 SwipeView 的边界,不裁剪的话内容会画出区域外、盖到旁边的控件上。SwipeView 相关场景把它当成标配。

页面和指示器是两层叠加PageIndicatorSwipeView 不是父子关系,而是兄弟------SwipeView 占满整个区域,指示器用 anchors.bottom 浮在 SwipeView 底部之上 20 像素。这样圆点永远压在页面内容上层。

自定义指示器

基础版只能显示固定样式的圆点,也不能点。这一版做两件事:把指示器的点换成"小圆点 + 数字",并让它可点击跳页;同时页面从"三个写死的子项"改成 Repeater 生成,配合 Loader 做到只创建当前页和左右相邻页。

演示代码

qml 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

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

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

        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: "transparent"

            SwipeView {
                id: swipeView
                anchors.fill: parent
                clip: true
                currentIndex: indicator.currentIndex

                Repeater {
                    model: 4
                    Loader {
                        active: SwipeView.isCurrentItem || SwipeView.isNextItem || SwipeView.isPreviousItem
                        sourceComponent: CustomRect {
                            description: "This is CustomRect" + (index+1)
                            Component.onCompleted: console.log("created:", index)
                            Component.onDestruction: console.log("destroyed:", index)
                        }
                    }
                }
            }

            PageIndicator {
                id: indicator
                interactive: true  // 启用点击跳转
                anchors {
                    bottom: parent.bottom
                    horizontalCenter: parent.horizontalCenter
                    bottomMargin: 20
                }

                count: swipeView.count
                currentIndex: swipeView.currentIndex
                visible: count > 1
                spacing: 10

                delegate: Column {
                    spacing: 10

                    Rectangle {
                        width: 12
                        height: 12
                        radius: 6
                        color: index === swipeView.currentIndex ? "#21be2b" : "#e0e0e0"

                        Behavior on color {
                            ColorAnimation { duration: 200 }
                        }
                    }

                    Text {
                        text: index + 1
                        color: "#21be2b"
                        font.bold: true
                        anchors.horizontalCenter: parent.horizontalCenter
                    }
                }
            }
        }
    }
}

关键逻辑解析

delegate 决定指示器每一项长什么样PageIndicator 的每一项默认是主题自带的圆点,重写 delegate 就能完全自定义。这里每一项是一个 Column:上面 12×12 的圆点,下面跟着页码数字,形成"○1 ○2 ○3 ○4"的竖排组合。

在 delegate 里用 index === swipeView.currentIndex 判断选中态 。delegate 的 index 从 0 开始,和 currentIndex 对上了就亮绿色,否则灰色。Behavior on color + ColorAnimation 让圆点切换颜色时有 200ms 的渐变,视觉上能看出"选中点在移动",而不是生硬跳变。

interactive: true 一行开启点击跳页 。开启后点任意一项,PageIndicator 会修改自己的 currentIndex。注意这里的同步方向:swipeView.currentIndex: indicator.currentIndex 让页面跟随指示器,同时 indicator.currentIndex: swipeView.currentIndex 又让指示器跟随手势滑动------两边互相引用,值一致时不会触发循环,最终效果就是"手滑页面、点圆点、两边永远同步"。

visible: count > 1 防尴尬。只有一页时圆点没有存在意义,条件为假自动隐藏。

页面数量多时用 Repeater + Loader 按需创建 。SwipeView 的全部子项都会被实例化,如果直接写 4 个 CustomRect 或者用 Repeater 平铺,启动时所有页面一次建完。改法是在 Repeater 里放 Loader,用 active 控制"当前页 + 前一页 + 后一页"才真正创建组件------滑动动画只需要相邻页在场,更远的页面等滑到附近再建,滑远了就销毁。demo 里的 console.log 就是让你在控制台看 created/destroyed 的先后,验证懒加载生效。

什么时候该用哪种写法

基础版 自定义版
页面写法 子项直接写死 Repeater + Loader 懒加载
页面数量 少、固定 多、可程序化生成
指示器外观 主题默认圆点 自定义 delegate
能否点击跳页 否(interactive 默认 false)

页面少且固定,直接把子项写进 SwipeView 最直观;页面多、结构重、想省启动开销,就换 Repeater + Loader 的懒加载写法。指示器想要"点哪跳哪"或改外观,一律重写 delegate 并开 interactive

运行验证

  1. Qt Creator 打开 qml_swipeview/CMakeLists.txt,按 Ctrl+R 运行;
  2. 左侧点「SwipeView 与指示器」,拖拽滑动看三个页面切换、底部圆点跟随;
  3. 点「自定义指示器」,滑动时看控制台 created/destroyed 日志(只有相邻页会被创建),再点数字圆点直接跳页,观察圆点绿色渐变过渡。

扩展复用方向

  • 页面改成 Image 就是图片轮播,改成 Loader 加载外部组件文件就能做"懒加载标签页";
  • 指示器圆点想换成进度条、缩略图甚至文字标签,都从改 delegate 入手;
  • interactive: true 后想监听用户主动跳页(区分手势翻页和点击跳页),在 onCurrentIndexChanged 里做埋点即可。

已验证环境

相关推荐
Quz1 小时前
QML TabBar组件与容器索引的绑定
qt
励志不掉头发的内向程序员3 小时前
【LibreCAD 2D架构】鼠标点下的坐标为什么会被“吸”走?LibreCAD 对象捕捉系统解析
开发语言·c++·qt·学习·系统架构
charlie11451419113 小时前
现代Qt开发之图像处理进阶:像素操作与格式转换
开发语言·图像处理·qt
Jackson_GJH16 小时前
Qt 右键自定义菜单的实现
开发语言·c++·qt
老赵的博客1 天前
c++ 之观察者模式
c++·qt
Quz1 天前
QML TabBar 自定义样式与动态标签
qt
似水এ᭄往昔1 天前
【Qt】--常用控件(输入类控件)
开发语言·qt
qq_401700411 天前
Qt无边框窗体移动详解
qt
励志不掉头发的内向程序员1 天前
【LibreCAD 2D架构】RS_Line创建之后发生了什么?从图形容器到屏幕渲染
开发语言·c++·qt·学习·系统架构