单个 Page 组件的骨架是静态的,界面真正动起来要靠切换容器(StackLayout、SwipeView)。Qt 里最常见的两种切换是「堆栈切换」和「滑动切换」,分别对应 StackLayout 和 SwipeView。这篇就用两个 demo 把 Page 装进这两个容器,顺带解决一个新手困惑:Page 的 title 到底怎么显示出来。
- StackLayout 切换 --- 按钮切换索引,页面状态全部保留
- SwipeView 滑动切换 --- 手势滑动翻页 +
PageIndicator圆点指示
Demo 1:Page 与 StackLayout
将三个 Page 叠加放入 StackLayout 里:

演示代码
qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
FadeInAnimation {
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// ... 省略标题组件 TitleSeparator ...
RowLayout {
Layout.fillWidth: true
spacing: 5
Repeater {
model: ["首页", "列表", "详情"]
Button {
text: modelData
highlighted: stackLayout.currentIndex === index
onClicked: stackLayout.currentIndex = index
}
}
}
StackLayout {
id: stackLayout
Layout.fillWidth: true
Layout.fillHeight: true
// StackLayout 不做页面标题渲染,当前页由上方按钮高亮指示,
// 故这里不设置 Page.title(title 只对能读取它的宿主有意义)
Page {
Label {
anchors.centerIn: parent
text: "首页页面"
font.pointSize: 14
}
}
Page {
ListView {
anchors.fill: parent
clip: true
model: 5
delegate: ItemDelegate {
text: "列表项 " + (index + 1)
}
}
}
Page {
Label {
anchors.centerIn: parent
text: "详情页面(索引 " + stackLayout.currentIndex + ")"
font.pointSize: 14
}
}
}
Item { Layout.fillHeight: true }
}
}
关键逻辑解析
切换就是改 currentIndex
StackLayout 的所有子项按顺序排成 0、1、2......页,currentIndex 指向谁就显示谁。按钮那行用 Repeater 组件从字符串数组批量生成三个 Button,每个按钮只干两件事:
qml
highlighted: stackLayout.currentIndex === index // 是当前页就高亮
onClicked: stackLayout.currentIndex = index // 点谁切到谁
不需要中间层信号转发,Button 直接读写 StackLayout 的索引,这是最省事的联动写法。
StackLayout 与 SwipeView 的本质区别
StackLayout 不是导航控件,它只是个「同位置叠放 + 按索引显示」的布局------所有子页同时存在,切换只是改谁可见。带来的特性:页面里的状态(比如列表滚动位置、输入框内容)切换后原样保留;但也意味着没有滑动动画、所有页面一开始就全部实例化。
所以 demo 里在 StackLayout 内放 ListView 很安全,切走再切回来滚动位置还在。
不要在 StackLayout 里设 Page.title
Page 有个 title 属性,但它自己不会渲染标题。
官方文档说得明白,title 是给宿主读取用的(StackView、Dialog 这类会读,或者你手动显示它)。StackLayout 不读 title,写了也没人看,反而会让新手误以为「设了 title 就有标题栏」。demo 里索性不设,当前页靠按钮高亮指示就够了。
Demo 2:Page 与 SwipeView
将三个带有颜色的 Page 组件放进 SwipeView 组件中,左右滑动翻页,PageIndicator 显示当前在第几页面,顶部标题会跟随页面切换。

演示代码
qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
FadeInAnimation {
id: demoRoot
// 桥接 SwipeView 当前页,便于宿主读取 Page.title(Page 自身不渲染标题)
property var currentPage: swipeView.currentItem
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// ... 省略标题组件 TitleSeparator ...
// Page 不负责绘制 title,标题由宿主(这里是 Label)读取 currentPage.title 显示
Label {
text: demoRoot.currentPage ? demoRoot.currentPage.title : ""
font.pointSize: 13
font.bold: true
color: "#1296FF"
Layout.alignment: Qt.AlignHCenter
}
SwipeView {
id: swipeView
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
Page {
title: "首页"
background: Rectangle { color: "#3498db" }
Label {
anchors.centerIn: parent
text: "首页内容"
color: "#fff"
font.pointSize: 16
}
}
// ... 第二、三页(发现 / 消息)结构与首页相同,
// 仅 title、背景色、文字不同 ...
Page {
title: "消息"
background: Rectangle { color: "#2ecc71" }
Label {
anchors.centerIn: parent
text: "消息内容"
color: "#fff"
font.pointSize: 16
}
}
}
PageIndicator {
count: swipeView.count
currentIndex: swipeView.currentIndex
Layout.alignment: Qt.AlignHCenter
}
Item { Layout.fillHeight: true }
}
}
关键逻辑解析
滑动 + 圆点指示,一行绑定
SwipeView 自带手势翻页,通过以下两个属性就能够和 PageIndicator 组件互动:
qml
count: swipeView.count // 圆点总数
currentIndex: swipeView.currentIndex // 当前圆点高亮
PageIndicator 只管显示,滑动逻辑全在 SwipeView 里。另外 clip: true 要带上,翻页动画时页面会横向滑出边界,不裁剪就会画到容器外面去。
用 background 给 Page 上色
上一篇讲过内容子项要自己写 Rectangle 填充,这里换了更省事的做法:直接把 Rectangle 塞进 Page.background。
qml
Page {
background: Rectangle { color: "#3498db" }
...
}
background 会自动铺满整个 Page(含 header/footer 区域),比在内容里垫矩形少一层嵌套。
Page.title 的正确打开方式
这就是 Demo 1 说的「宿主读取 title」的实例。顶部那行蓝色 Label 绑定当前页:
qml
text: demoRoot.currentPage ? demoRoot.currentPage.title : ""
currentPage 是 swipeView.currentItem 的桥接属性------注意 currentItem 的静态类型是 Item,直接读 .title 静态检查会报找不到属性,用一个 property var 中转后运行时拿到的是真实的 Page 对象,.title 就有效了。
标题不跟着滑动动画走、只在 currentIndex 切换瞬间更新,够用且简单。
StackLayout 还是 SwipeView
| 场景 | 用哪个 |
|---|---|
| 顶部 Tab/按钮导航,切换要保留各页状态 | StackLayout |
| 移动端手势翻页、左右滑动的引导页 | SwipeView |
运行验证
- 用 Qt Creator 打开
qml_page/CMakeLists.txt; - 按
Ctrl+R运行; - StackLayout demo:点「首页 / 列表 / 详情」按钮看切换与高亮,把列表滚到中间再切走切回,确认滚动位置保留;
- SwipeView demo:左右滑动翻页,观察顶部标题和底部圆点同步变化,翻页过程内容不会画出容器边界。
扩展复用方向
- 给
StackLayout页面切增加淡入淡出:每个子页外包一层opacity+Behavior动画即可。 SwipeView配TabBar是移动 App 底部导航的经典组合:TabBar.currentIndex与SwipeView.currentIndex双向绑定。PageIndicator换个交互逻辑就能当「轮播图指示器」用,配Timer自动翻页就是广告轮播。
已验证环境:
- Qt 版本:Qt 6.8.2 / Qt 6.11.1
- 操作系统:Windows 11
- GitHub:QML-Minimal-Demos/qml_page