一是把 SwipeView 和 TabBar 绑定在一起,将页签点击与页面滑动操作连接起来,做成 App 常见的"顶部标签 + 内容页"结构;二是给每个页面套上 Loader,用日志区实时观察页面创建与销毁,确认懒加载真的只建了该建的页面。
- SwipeView 与 TabBar 联动 — 页签点击和手势滑动双向同步
- SwipeView 与 Loader — 按需创建页面,日志区验证懒加载过程
SwipeView 与 TabBar
数据源用一个 ListModel 统一管理,TabBar 和 SwipeView 各自用 Repeater 遍历同一份数据生成页签和页面。点页签切页面,滑动页面页签也跟着走,两边永远一致。
演示代码
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
FadeInAnimation {
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// ... 省略标题组件 TitleSeparator ...
ListModel {
id: listModel
ListElement { demoText: "Page1"; demoDes: "This is Page1" }
ListElement { demoText: "Page2"; demoDes: "This is Page2" }
ListElement { demoText: "Page3"; demoDes: "This is Page3" }
}
TabBar {
id: tabBar
Layout.fillWidth: true
currentIndex: swipeView.currentIndex
Repeater {
model: listModel
delegate: TabButton {
text: demoText
onClicked: swipeView.currentIndex = index
}
}
}
SwipeView {
id: swipeView
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
Repeater {
model: listModel
delegate: CustomRect {
description: demoDes
}
}
}
}
}
关键逻辑解析
同步的关键是 currentIndex,方向是"手势滑动驱动页签"。tabBar.currentIndex 直接绑到 swipeView.currentIndex:滑动翻页时 SwipeView 先改自己的 currentIndex,TabBar 收到变化自动高亮对应页签——这是 TabBar 作为"跟随方"的绑定。
点页签时反向写回 swipeView.currentIndex。TabButton.onClicked 里执行 swipeView.currentIndex = index,SwipeView 翻到对应页,页签高亮又由上一行绑定同步回来。两个方向凑齐,双向联动成立,而且没有双向绑定造成的循环隐患:只有一处绑定(TabBar ← SwipeView),点击是命令式赋值。
一个 ListModel 喂两个 Repeater,保证页签和页面数量天然一致。TabBar 的 delegate 取 demoText 当页签文字,SwipeView 的 delegate 取 demoDes 当页面内容。增删数据只需改 listModel,页签和页面同步增减,不会出现"页签 3 个、页面 2 个"的错位。
注意这个 demo 没有用 interactive 指示器,页签本身就是导航。TabBar 自带选中态高亮和均分宽度,比 PageIndicator 更适合"多、需要文字说明"的页面;指示器适合"少、只有圆点"的场景。
SwipeView 与 Loader
5 个页面由一个 Repeater 按 model: 5 生成,但每个页面都包在 Loader 里,只有"当前页和左右相邻页"才真正实例化。底部日志区实时打印每页的 created/destroyed,滑动时能看到页面按需创建、滑远后销毁,而不是一次建满 5 个。
演示代码
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
FadeInAnimation {
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// ... 省略标题组件 TitleSeparator ...
SwipeView {
id: swipeView
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
currentIndex: indicator.currentIndex
Repeater {
model: 5
Loader {
active: SwipeView.isCurrentItem || SwipeView.isNextItem || SwipeView.isPreviousItem
sourceComponent: CustomRect {
description: "This is CustomRect" + (index+1)
Component.onCompleted: logArea.message.append("created: CustomRect" + index)
Component.onDestruction: logArea.message.append("destroyed: CustomRect" + index)
}
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.minimumHeight: 100
Layout.maximumHeight: 100
Layout.margins: 10
border.width: 1
border.color: "#ccc"
radius: 6
LogArea {
id: logArea
anchors.fill: parent
}
}
CustomPageIndicator {
id: indicator
Layout.alignment: Qt.AlignHCenter
count: swipeView.count
currentIndex: swipeView.currentIndex
visible: count > 1
}
}
}
页面组件的日志往 logArea 上追加(LogArea 是基于 TextArea 的自制滚动日志区),指示器复用项目里的 CustomPageIndicator——一个封装好 delegate、开了 interactive 的 PageIndicator。
关键逻辑解析
active 三选一是懒加载的开关。SwipeView.isCurrentItem、isNextItem、isPreviousItem 是 SwipeView 给每个页面子项提供的附加属性,分别表示"我是当前页 / 我是下一页 / 我是上一页"。三个条件用 || 连起来:只要页面处在当前、左邻或右邻位置,Loader.active 为 true,组件才被创建;滑到更远的位置条件变 false,页面被销毁。这是上一篇提到懒加载写法的完整落地。
为什么是"当前 ± 1"而不是只建当前页?滑动动画过程中,手指拖到一半屏幕同时露着两页——如果只预建当前页,拖出下一页的瞬间才去创建,会出现白屏闪烁。保留左右邻页,动画随时有内容可画;而隔了两三页的远端页面滑过去还需要时间,没必要提前占内存。
日志区把"创建/销毁"变成可观察的过程。页面在 Component.onCompleted 和 Component.onDestruction 里各打一条日志,滑到第 3 页时控制台会看到前两页陆续 destroyed、后两页陆续 created——懒加载到底省了多少实例,一眼可见。
currentIndex: indicator.currentIndex 让指示器成为唯一输入源。这里没让 SwipeView 自己管页码,而是指示器点哪页、SwipeView 就翻到哪页;用户手势滑动时 currentIndex 变化又回写给指示器高亮,和上一篇"自定义指示器"的双向同步是同一套路。
TabBar 方案和 Loader 方案怎么选
| TabBar 联动 | Loader 懒加载 | |
|---|---|---|
| 导航形式 | 文字页签 | 圆点指示器 |
| 页面管理 | 常驻,全部创建 | 只建当前页 ± 相邻页 |
| 适合场景 | 页签少、内容轻、要导航可见 | 页多、单页重、省启动开销 |
| 内容来源 | `ListModel` 驱动,可增删 | `Repeater` + `Loader` |
两者不冲突:想让"重页面"也有文字页签导航,就把 TabBar 联动和 Loader 懒加载拼在一起——页签驱动 currentIndex,页面用 active 条件按需创建。
运行验证
- Qt Creator 打开
qml_swipeview/CMakeLists.txt,按Ctrl+R运行; - 点「SwipeView 与 TabBar」,点 Page1/2/3 页签或直接滑动内容区,观察两者始终同步;
- 点「SwipeView 与 Loader」,连续滑动页面,看日志区滚动输出 created/destroyed,再点底部圆点跳页,观察跳远页时中间页的创建销毁顺序。
扩展复用方向
- 想让 TabBar 方案也省内存,把 SwipeView 里的 delegate 换成
Loader+active三条件即可,TabBar 联动逻辑不用动; - 页面是外部
.qml文件时,把sourceComponent换成source,就能做成"按文件名懒加载页面"的通用宿主; - 日志区的做法可以推广到任何想看组件生命周期的调试场景——
Component.onCompleted/onDestruction打点即可。
已验证环境:
- Qt 版本:Qt 6.8.2 / Qt 6.11.1
- 操作系统:Windows 11
- GitHub:QML-Minimal-Demos/qml_swipeview
TabBar 联动与 Loader 懒加载两条路线讲得很透,active 三条件与日志验证是可直接复用的写法,适合 QML 中大型页面导航选型参考。