TabBar 组件只能处理当前选第几个的需求,真正展示内容要靠容器、布局等组件。Qt 里有两类容器能够和 TabBar 配对,分别是 StackLayout(用索引控制页面)和 SwipeView(支持手势滑动)。
这篇分别介绍它们的关联用法:
- TabBar + StackLayout --- 点击标签即时切换内容页
- TabBar + SwipeView --- 标签和页面
currentIndex双向绑定,点击、滑动都能切换页面
TabBar 与 StackLayout:点击标签切换页面

三个标签对应三块纯色内容区,点标签内容立刻切换,底部显示"当前索引 / 总页数"。
演示代码
qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
FadeInAnimation {
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// ... 省略标题组件 TitleSeparator ...
TabBar {
id: tabBar
Layout.fillWidth: true
TabButton {
text: "页面1"
width: implicitWidth
}
TabButton {
text: "页面2"
width: implicitWidth
}
TabButton {
text: "页面3"
width: implicitWidth
}
}
StackLayout {
id: stackLayout
Layout.fillWidth: true
Layout.fillHeight: true
currentIndex: tabBar.currentIndex
Rectangle {
color: "#3498db"
Text {
anchors.centerIn: parent
text: "页面 1"
color: "#fff"
font.pointSize: 20
font.bold: true
}
}
Rectangle {
color: "#e74c3c"
Text {
anchors.centerIn: parent
text: "页面 2"
color: "#fff"
font.pointSize: 20
font.bold: true
}
}
Rectangle {
color: "#2ecc71"
Text {
anchors.centerIn: parent
text: "页面 3"
color: "#fff"
font.pointSize: 20
font.bold: true
}
}
}
Text {
text: "当前索引: " + tabBar.currentIndex + " / 总页数: " + stackLayout.count
font.pointSize: 11
color: "#333"
}
}
}
关键逻辑解析
联动只有一行:currentIndex: tabBar.currentIndex 。把 StackLayout 的 currentIndex 绑到 TabBar 上,点标签 → TabBar 的索引变 → StackLayout 显示对应那一页。TabBar 自身不需要知道内容区存在,这一行绑定是它们唯一的连接点。
StackLayout 的页面全部常驻 。三个 Rectangle 从一开始就都创建好了,切换只是"显示第几个",不是"创建第几个"。所以页面里的输入框、滚动位置这些内部状态切换后原样保留,代价是所有页面占用的内存、初始化开销一直存在。
stackLayout.count 直接可读 。页面数就是 StackLayout 的子项数,运行时也能查。底部文字把 tabBar.currentIndex 和 count 拼在一起,一眼看出"第几页 / 共几页"。
按钮设 width: implicitWidth,标签按内容宽排列。不给宽度 TabBar 会把按钮拉满均分;这里显式设成内容宽度,三个标签靠左紧凑排列,突出"这是页内切换而非全宽导航"的用法。
TabBar 与 SwipeView:双向联动

四个标签,下方四个页面。点标签可以换页,按住页面左右滑也可以换页------两个操作都同步到对方的选中状态。
演示代码
qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
FadeInAnimation {
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// ... 省略标题组件 TitleSeparator ...
TabBar {
id: tabBar
Layout.fillWidth: true
currentIndex: swipeView.currentIndex
Repeater {
model: ["首页", "发现", "消息", "我的"]
TabButton {
text: modelData
ToolTip.visible: hovered
ToolTip.text: "索引: " + TabBar.index
}
}
}
SwipeView {
id: swipeView
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
// 使用 currentIndex 进行双向绑定
currentIndex: tabBar.currentIndex
Repeater {
model: ["#3498db", "#e74c3c", "#2ecc71", "#f39c12"]
Rectangle {
color: modelData
Text {
anchors.centerIn: parent
text: "页面 " + (index + 1)
color: "#fff"
font.pointSize: 18
font.bold: true
}
}
}
}
Text {
text: "当前索引: " + swipeView.currentIndex + " / 总页数: " + swipeView.count
font.pointSize: 11
color: "#333"
}
}
}
关键逻辑解析
双向同步 = 两个 currentIndex 互相绑定 。tabBar.currentIndex: swipeView.currentIndex 和 swipeView.currentIndex: tabBar.currentIndex 同时存在:点标签时,TabBar 索引变化传到 SwipeView 完成翻页;滑动页面时,SwipeView 索引变化传回 TabBar 让高亮跟着走。这是 Qt 文档里推荐的"two-way binding"写法,注意要点是不要在事件里直接给 currentIndex 赋值,赋值会切断绑定,改动要交给绑定系统完成。
clip: true 是滑动的标配。SwipeView 翻页是横向滑动过渡,翻页瞬间两个页面同时出现在可视区,不裁剪会把旁边的页面画出来,观感就是"露馅"。
标签数据用字符串数组驱动 。Repeater 的 model 直接给 ["首页", "发现", "消息", "我的"],delegate 里用 modelData 取当前项当文字------想加标签,往数组里加一个字符串就行。
TabBar.index 是官方附加属性,不是笔误 。TabBar 会给它的每个 TabButton 附加 index、position、tabBar 三个只读属性,delegate 里写 "索引: " + TabBar.index 读到的就是"这个按钮是第几个"。示例把它接进 ToolTip,鼠标悬停标签时显示当前索引,是演示附加属性最直观的方式。
ToolTip 挂在控件上是附加式用法 。ToolTip.visible: hovered + ToolTip.text: ... 直接写在 TabButton 上,不用实例化 ToolTip 组件,悬停自动弹出,移动端没有鼠标则天然不触发。
StackLayout 还是 SwipeView:看要不要手势和动画
| StackLayout | SwipeView | |
|---|---|---|
| 切换方式 | 点标签即时跳 | 点标签 / 手势滑动 |
| 切换动画 | 无,直接显示 | 有,页面滑入滑出 |
| 页面生命周期 | 全部常驻 | 相邻页预加载,仍保留状态 |
| 适合 | 表单、设置项这类"别丢状态"的页 | 内容流、需要手势翻页的浏览场景 |
| 联动 TabBar | 单向绑定即可 | 需双向绑定 |
判断标准很简单:要不要滑动翻页的体验。纯工具型页面用 StackLayout 干净利落;偏浏览型(图片流、文章列表)用 SwipeView,配合手势更顺手。固定数量页面用这两者都行,标签本身要动态增删的话,回到上一篇的 Repeater + ListModel 方案,把内容区的 Repeater model 也接同一份数据即可。
运行验证
- Qt Creator 打开
qml_tabbar/CMakeLists.txt,按Ctrl+R运行; - 左侧点「与 StackLayout」,点三个标签看内容即时切换、底部索引联动;
- 点「与 SwipeView」,先点标签换页,再按住页面左右拖拽换页,观察标签高亮始终跟着当前页;鼠标悬停标签可看到索引提示。
扩展复用方向
- 想在 StackLayout 切换时也带点过渡,可以给页面加
visible与透明度状态,或改用StackView的 push/pop 做带动画的层级导航; - SwipeView 每个页面顶部配一个
PageIndicator小圆点,滑动位置一目了然,是引导页、轮播的现成组合; - 双向绑定出问题时,先检查是不是有代码在
onClicked里直接写过currentIndex = x------那会切断绑定,改成用tabBar.setCurrentIndex(x)这类方法赋值。
已验证环境:
- Qt 版本:Qt 6.8.2 / Qt 6.11.1
- 操作系统:Windows 11
- GitHub:QML-Minimal-Demos/qml_tabbar