本文主要介绍 qml_navigation_layout(QML导航布局示例工程,下载链接在结尾处),整合了 Qt Quick Controls 开发中最常用的四种导航样式:顶部导航、抽屉式导航、侧边导航栏、底部导航栏,几乎覆盖了日常软件开发中所有主流的导航界面场景。
提前分清:两个极易混淆的页面切换组件
在动手实操四种导航布局前,先吃透两个名字相似、用法和场景完全不同的核心组件,这是避开新手误区、理解导航切换逻辑的关键。
1. StackLayout 堆叠布局
StackLayout(堆叠布局) 的核心逻辑是将所有子页面层层堆叠在一起,同一时间只展示单个页面。它依靠 currentIndex 属性精准控制当前显示的页面,页面切换为瞬时完成,无任何过渡动画。
该组件专门适配平级页面切换场景,比如常见的标签页 Tab 切换,页面之间无先后顺序、无层级隶属关系,仅需快速切换展示内容。
2. StackView 页面栈
StackView(页面栈) 用于管理存在先后层级关系的页面,和 StackLayout 的平级逻辑完全不同。
可以简单理解为页面堆栈:需要打开新页面时,调用 push 方法将页面压入栈中;需要返回上一页时,调用 pop 方法弹出当前页面。页面栈始终只展示最顶层页面,完整记录了页面跳转轨迹,天然支持前进、后退操作,还能为页面进出配置专属转场动画,适配各类层级跳转业务。
核心组件选型总结
- 页面为平级并列关系 、仅需快速切换(标签页切换),优先使用 StackLayout;
- 页面存在层级跳转、返回逻辑 ,需要转场动画,优先使用 StackView;
- 本工程中的抽屉式导航为特殊实现,无需使用以上两个组件,内容区域直接通过矩形组件搭建,逻辑更简洁直观。
Demo 1 顶部导航栏

顶部导航栏采用经典的三段式布局。内容区域由 StackView 页面栈全权管理,页面前进时,新页面从右侧滑入、旧页面向左滑出。
演示代码
yaml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
FadeInAnimation {
id: root
QtObject {
id: nav
property int currentIndex: 0
property var pages: [
{ name: "首页", color: "#3498db" },
{ name: "文件", color: "#9b59b6" },
{ name: "设置", color: "#e67e22" }
]
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// ... 省略标题组件 TitleSeparator ...
RowLayout {
Layout.fillWidth: true
spacing: 10
Button {
text: "<<"
enabled: nav.currentIndex > 0
onClicked: {
nav.currentIndex--
stackView.pop()
}
}
Text {
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
text: nav.pages[nav.currentIndex].name
font.pointSize: 10
}
Button {
text: ">>"
enabled: nav.currentIndex < nav.pages.length - 1
onClicked: {
nav.currentIndex++
// 页面索引通过参数注入:若让每个页面都绑定 nav.currentIndex,
// 切换时栈内所有页面会一起变色。
stackView.push(pageComponent, { "idx": nav.currentIndex })
}
}
}
StackView {
id: stackView
Layout.fillWidth: true
Layout.fillHeight: true
// 转场时页面从边界外滑入,必须裁剪,否则会溢出到左侧导航栏
clip: true
initialItem: pageComponent
pushEnter: Transition {
PropertyAnimation {
property: "x"
from: stackView.width
to: 0
duration: 200
}
PropertyAnimation {
property: "opacity"
from: 0
to: 1
duration: 200
}
}
// ... 省略 pushExit / popEnter / popExit 三组转场,写法对称、方向相反 ...
}
}
Component {
id: pageComponent
Rectangle {
id: pageRect
property int idx: 0
color: nav.pages[pageRect.idx].color
radius: 4
Text {
anchors.centerIn: parent
text: nav.pages[pageRect.idx].name + "内容"
color: "#fff"
font.pointSize: 12
font.bold: true
}
}
}
}
关键逻辑解析
本示例采用职责分离的清晰设计:StackView 专门负责页面渲染和层级跳转,而 nav.currentIndex 仅控制顶部标题与切换按钮状态。两者解耦,从根本上避免标题和页面状态不匹配的问题。
核心实用开发小技巧:通过 push(pageComponent, { "idx": nav.currentIndex }) 以传参方式为新页面注单独传入索引值参数,页面通过以 property int idx 接收使用。禁止页面不要直接绑定全局的 nav.currentIndex,否则切换页面时,栈内所有历史页面会同步刷新样式,引发逻辑错误出现异常 bug。
适用场景:向导式多步流程、软件设置页层级跳转、需要「返回」的层级导航场景。
Demo 2 抽屉式导航

抽屉式导航是轻量化临时导航方案,默认隐藏不占用屏幕空间,点击按钮即可从内容区左侧滑出深色菜单面板。点击菜单项切换页面,或点击背景遮罩,均可收起抽屉面板,交互简洁轻便。
演示代码
yaml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
FadeInAnimation {
id: root
QtObject {
id: drawerNav
property int currentIndex: 0
property bool isOpen: false
property var pages: [
{ name: "首页", color: "#3498db" },
{ name: "消息", color: "#9b59b6" },
{ name: "设置", color: "#e67e22" }
]
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// ... 省略标题组件 TitleSeparator ...
RowLayout {
Layout.fillWidth: true
spacing: 10
Button {
implicitWidth: 120
implicitHeight: 40
text: drawerNav.isOpen ? "关闭菜单" : "打开菜单"
onClicked: drawerNav.isOpen = !drawerNav.isOpen
}
Text {
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
text: "当前页面: " + drawerNav.pages[drawerNav.currentIndex].name
font.pointSize: 10
color: "#666"
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
radius: 4
color: drawerNav.pages[drawerNav.currentIndex].color
Behavior on color {
ColorAnimation {
duration: 200
}
}
Text {
anchors.centerIn: parent
text: drawerNav.pages[drawerNav.currentIndex].name + "内容"
color: "#fff"
font.pointSize: 14
font.bold: true
}
}
}
// 遮罩层:覆盖整个内容区,点击关闭抽屉
Rectangle {
id: drawerMask
anchors.fill: parent
color: "#80000000"
visible: drawerNav.isOpen
z: 1
MouseArea {
anchors.fill: parent
onClicked: drawerNav.isOpen = false
}
}
// 抽屉面板:始终位于内容区内,关闭时滑出左侧边界由容器裁剪
Rectangle {
id: drawerPanel
anchors.top: parent.top
anchors.bottom: parent.bottom
width: 140
x: drawerNav.isOpen ? 0 : -width
color: "#34495e"
z: 2
Behavior on x {
NumberAnimation {
duration: 200
easing.type: Easing.OutQuad
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 5
Text {
text: "导航菜单"
font.pointSize: 11
font.bold: true
color: "#fff"
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
}
Repeater {
model: drawerNav.pages
Rectangle {
id: menuItem
required property int index
required property var modelData
Layout.fillWidth: true
Layout.preferredHeight: 34
radius: 4
color: drawerNav.currentIndex === menuItem.index ? menuItem.modelData.color : "transparent"
Text {
anchors.centerIn: parent
text: menuItem.modelData.name
font.pointSize: 10
color: "#fff"
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
drawerNav.currentIndex = menuItem.index
drawerNav.isOpen = false
}
}
}
}
Item {
Layout.fillHeight: true
}
}
}
}
关键逻辑解析
抽屉导航的代码逻辑非常轻量化,依靠单一 isOpen 状态即可统一控制面板显隐、位移动画和遮罩展示。按钮、遮罩、菜单项的所有交互,本质都是切换这个状态值,代码简洁、维护成本极低。
滑出动画无需手动触发,借助 Behavior on x 监听位移属性变化,搭配 200ms 缓动动画,即可实现贴合系统交互的自然滑出效果,是 QML 属性动画最简洁的实现方式。
新手需要注意两个细节:通过 z 层级区分遮罩与面板,避免面板被遮挡无法点击;依靠根节点 clip 裁剪,让抽屉关闭时可以完全隐藏,不会残留边缘画面。遮罩透明度可通过颜色值前两位数值自由调整。
适用场景:导航项数量多、无需常驻展示的界面,移动端汉堡菜单、软件临时侧边设置面板、小屏设备适配场景。
Demo 3 侧边导航栏

侧边导航栏与抽屉式导航逻辑相反,属于常驻型导航。
演示代码
yaml
import QtQuick
import QtQuick.Layouts
FadeInAnimation {
id: root
QtObject {
id: leftNav
property int currentIndex: 0
property var pages: [
{ name: "首页", color: "#3498db" },
{ name: "文件", color: "#9b59b6" },
{ name: "设置", color: "#e67e22" }
]
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// ... 省略标题组件 TitleSeparator ...
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 10
Rectangle {
Layout.preferredWidth: 60
Layout.fillHeight: true
radius: 4
color: "#f0f0f0"
ColumnLayout {
anchors.fill: parent
anchors.margins: 4
spacing: 4
Repeater {
model: leftNav.pages
Rectangle {
id: navItem
required property int index
required property var modelData
Layout.fillWidth: true
Layout.preferredHeight: 36
radius: 4
color: leftNav.currentIndex === navItem.index ? navItem.modelData.color : "transparent"
Text {
anchors.centerIn: parent
text: navItem.modelData.name
font.pointSize: 10
color: leftNav.currentIndex === navItem.index ? "#fff" : "#333"
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: leftNav.currentIndex = navItem.index
}
}
}
// 撑开剩余空间,让导航项顶部对齐
Item {
Layout.fillHeight: true
}
}
}
StackLayout {
Layout.fillWidth: true
Layout.fillHeight: true
currentIndex: leftNav.currentIndex
Repeater {
model: leftNav.pages
Rectangle {
id: contentPage
required property var modelData
color: contentPage.modelData.color
radius: 4
Text {
anchors.centerIn: parent
text: contentPage.modelData.name + "内容"
color: "#fff"
font.pointSize: 12
font.bold: true
}
}
}
}
}
}
}
关键逻辑解析
导航项通过 Repeater 批量生成,通过索引比对动态切换选中样式。底部空白 Item 利用填充高度特性,撑开剩余布局空间,让导航项置顶整齐排列,避免均匀分散、样式错乱。
内容区依托 StackLayout 实现平级瞬时切换,逻辑简单高效。它和抽屉导航的核心区别在于展示形态:侧边栏常驻页面、随时可视,抽屉导航默认隐藏、按需唤起,可根据屏幕空间自由选择。
适用场景:桌面端软件主界面、工具类应用模块切换、需要导航与内容长期同屏展示的场景。
Demo 4 底部导航栏

移动端 App 最主流的导航样式。
演示代码
yaml
import QtQuick
import QtQuick.Layouts
FadeInAnimation {
id: root
QtObject {
id: bottomNav
property int currentIndex: 0
property var pages: [
{ icon: "qrc:/icons/home.svg", label: "首页", color: "#3498db" },
{ icon: "qrc:/icons/search.svg", label: "发现", color: "#e74c3c" },
{ icon: "qrc:/icons/message.svg", label: "消息", color: "#f39c12" },
{ icon: "qrc:/icons/user.svg", label: "我的", color: "#9b59b6" }
]
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// ... 省略标题组件 TitleSeparator ...
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
radius: 4
StackLayout {
anchors.fill: parent
currentIndex: bottomNav.currentIndex
Repeater {
model: bottomNav.pages
Rectangle {
id: tabPage
required property var modelData
color: tabPage.modelData.color
Text {
anchors.centerIn: parent
text: tabPage.modelData.label
color: "#fff"
font.pointSize: 14
font.bold: true
}
}
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 44
color: "#f8f9fa"
radius: 4
border.color: "#dee2e6"
RowLayout {
anchors.fill: parent
anchors.margins: 5
spacing: 4
Repeater {
model: bottomNav.pages
Rectangle {
id: tabButton
required property int index
required property var modelData
Layout.fillWidth: true
Layout.fillHeight: true
color: bottomNav.currentIndex === tabButton.index ? "#e3f2fd" : "transparent"
radius: 4
ColumnLayout {
anchors.centerIn: parent
spacing: 2
Image {
source: tabButton.modelData.icon
sourceSize.width: 18
sourceSize.height: 18
Layout.alignment: Qt.AlignHCenter
}
Text {
text: tabButton.modelData.label
font.pointSize: 8
color: bottomNav.currentIndex === tabButton.index ? tabButton.modelData.color : "#666"
Layout.alignment: Qt.AlignHCenter
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: bottomNav.currentIndex = tabButton.index
}
}
}
}
}
}
}
关键逻辑解析
底部导航最大的优势是数据与视图解耦,通过一份pages 数组同时驱动顶部内容区和底部 Tab 栏。新增、删减导航页面只需修改数组,界面会自动同步更新,代码复用率高、维护简单。
图标设置固定 sourceSize 解码尺寸,避免 SVG 原图尺寸异常导致 Tab 布局变形。默认使用 StackLayout 实现快速 Tab 切换,若需要左右滑动效果,直接替换为 SwipeView 即可,拓展性良好。
适用场景:移动端 App 主界面、多平级模块快速切换、便捷操作的移动端交互场景。
四种导航方案选型对比
| 维度 | 顶部导航栏 | 抽屉式导航 | 侧边导航栏 | 底部导航栏 |
|---|---|---|---|---|
| 导航条位置 | 内容区上方 | 覆盖内容区,按需弹出 | 内容区左侧常驻 | 内容区下方 |
| 切换组件 | StackView 页面栈 | 无需切换组件 | StackLayout | StackLayout |
| 切换动效 | 滑动+淡入淡出 | 面板位移+遮罩渐变 | 瞬时切换,无动画 | 瞬时切换,无动画 |
| 页面历史 | 保留,支持返回上一级 | 不保留页面历史 | 不保留页面历史 | 不保留页面历史 |
| 屏幕占位 | 顶部窄横条,常驻 | 默认零占位,临时占用 | 左侧竖栏,常驻 | 底部窄横条,常驻 |
| 页面关系 | 有层级、有先后顺序 | 纯平级并列 | 纯平级并列 | 纯平级并列 |
快速选型核心思路
新手选型无需死记硬背,只需看两个核心条件:
-
看页面关系:页面存在层级跳转、需要返回上一步的业务场景,优先选顶部导航(StackView 页面栈);所有纯平级、仅需来回切换的页面,选择剩余三种方案即可。
-
看屏幕空间与操作习惯:桌面端屏幕空间充足,优先用常驻式侧边导航,操作直观;小屏设备、导航项较多时,用抽屉导航节省空间;移动端 App 优先用底部导航,贴合拇指单手操作习惯。
四种导航方案并非互斥,实际开发中可组合使用:常见搭配为底部 Tab 导航管理 APP 一级核心模块,每个模块内部通过顶部导航实现二级页面层级跳转,侧边导航、抽屉导航适配特殊常驻/临时页面场景,适配绝大多数复杂界面需求。
运行验证环境
- Qt 适配版本:Qt6.8.2、Qt 6.11.1
- 运行系统:Windows 11
- 开源仓库:QML-Minimal-Demos/qml_navigation_layout