这一篇用两个示例说明入栈、出栈、替换的基本操作与批量操作。
基本操作
一个 StackView 配四个按钮:Push 压入新页、Push(无动画)再压一页、Pop 回到上一页、Replace 把当前页换掉。
演示代码
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
FadeInAnimation {
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// ... 省略标题组件 TitleSeparator ...
StackView {
id: stack
initialItem: mainView
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 15
clip: true
}
Text {
text: "StackView Depth: " + stack.depth
color: "#333"
font.pointSize: 11
}
}
Component {
id: mainView
ColumnLayout {
spacing: 10
property color textColor: "#333"
Button {
text: "Push(叠加新页面)"
Layout.fillWidth: true
Layout.preferredHeight: 30
property var info: {"textColor":"green"}
onClicked: stack.push(mainView, info)
}
Button {
text: "Push(叠加新页面,无动画)"
Layout.fillWidth: true
Layout.preferredHeight: 30
property var info: {"textColor":"green"}
onClicked: stack.push(mainView, info, StackView.Immediate)
}
Button {
text: "Pop(回到上一页)"
Layout.fillWidth: true
Layout.preferredHeight: 30
enabled: stack.depth > 1
onClicked: stack.pop()
}
// 栈长度不变,新的页面替换掉原有页面,退不回去
Button {
text: "Replace(替换当前页面)"
Layout.fillWidth: true
Layout.preferredHeight: 30
property var info: {"textColor":"red"}
onClicked: stack.replace(mainView, info)
}
Item { Layout.fillHeight: true }
}
}
}
关键逻辑解析
initialItem属性决定栈底是谁。StackView 一出生栈里就有 mainView(按钮页),所以 depth(栈深) 初始为 1。后续所有 Push 都叠在它上面,Pop 无论如何不会把栈底弹掉。
push(item, properties, operation) 的三个参数各管一件事:第一个是"推什么"——这里传组件 mainView,每次 push 都会新建一个页面实例;第二个是"给新页面带什么属性",{"textColor":"green"} 会按名注入到新页面的同名属性上(mainView 里声明了 property color textColor);第三个是操作方式,StackView.Immediate 表示不播转场动画、瞬间切换。replace(mainView, info) 同理,但效果是"原地换皮"——栈长度不变,回退时跳不到被替换掉的那一页。
栈底页没有上一页可回,Pop 按钮置灰避免空弹;这也说明 demo 是用 depth 做状态判断,而不是自己记一个计数器——页面压入弹出都由 StackView 管理,所以基于 depth 属性来判断永远是准的。
每次 Push 一个页面实例,按钮也带走一份。Push 压入的是 mainView 组件的副本,所以新页面里同样有这套按钮,可以继续 Push、Pop,页面就这样一层层叠下去。底部 Text 绑定 stack.depth,压一页数字加一,弹一页数字就减一,能直观看到栈的变化。
批量操作
基本操作的 API 一次只动一层。批量场景则要"一次压 5 页、一次弹 5 页、一键回栈底"——用的还是 push/pop,只是换成批量调用,外加 pushItems 这个专为连续压栈准备的接口。
演示代码
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
FadeInAnimation {
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
spacing: 15
// ... 省略标题组件 TitleSeparator ...
StackView {
id: stack
initialItem: mainView
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 15
clip: true
}
Text {
text: "StackView Depth: " + stack.depth
color: "#333"
font.pointSize: 11
}
}
Component {
id: mainView
ColumnLayout {
spacing: 10
property color textColor: "#333"
Button {
text: "批量Push(5个页面)"
Layout.fillWidth: true
Layout.preferredHeight: 30
enabled: stack.depth < 30
onClicked: {
var items = []
for (var i = 1; i <= 5; i++) {
items.push(mainView, {"textColor":"green"})
}
stack.pushItems(items)
}
}
Button {
text: "批量Pop(5个页面)"
Layout.fillWidth: true
Layout.preferredHeight: 30
enabled: stack.depth > 1
onClicked: {
var popCount = Math.min(5, stack.depth - 1)
for (var i = 0; i < popCount; i++) {
stack.pop()
}
}
}
Button {
text: "清空栈(Pop全部)"
Layout.fillWidth: true
Layout.preferredHeight: 30
enabled: stack.depth > 1
onClicked: {
while (stack.depth > 1) {
stack.pop()
}
}
}
Item { Layout.fillHeight: true }
}
}
}
关键逻辑解析
pushItems 接收的是"交替数组"。它要求数组元素按 item, properties, item, properties… 的顺序排列,每两项构成一次压栈。demo 的循环里 items.push(mainView, {...}) 一次塞进两个元素,循环 5 次得到 10 个元素的数组,pushItems 依次压入 5 页。注意它不会中途停:一批压栈视为一次连续操作,页面按顺序全部进入。
批量 Pop 本质是循环 Pop,要点在"算准次数"。Math.min(5, stack.depth - 1) 防止两件事:一是栈里不够 5 层时只弹能弹的;二是 stack.depth - 1 保证不把栈底页弹掉。stack.depth 在每次 pop() 后都会实时变小,所以循环里不能预先存死一个总数再用它当条件。
清空栈用 while (depth > 1)。栈底 initialItem 被保留,所以循环条件是 > 1 而不是 > 0。这其实是"弹到只剩栈底"的朴素写法,官方有更短的等价调用:pop(null) 会一直弹到只剩第一项,pop(首页引用) 则弹到指定页成为栈顶,都不用手写循环。demo 用 while 是为了直观展示 depth 每弹一次实时变小。
和 clear() 区分开。StackView 还有 clear() 方法,它把栈里所有项清空、包括初始项——清完栈里什么都没有,得再 push 一个页面才有内容。demo 里要保留首页当按钮页,所以不能用 clear;"整个栈推倒重来"的场景才轮到它。
批量按钮同样用 depth 做可用性判断。enabled: stack.depth < 30 给批量 Push 设了个"天花板"(每个页面实例里都能继续压 5 层,不设限会无限叠下去),纯防演示时把栈压爆;批量 Pop 和清空栈则在 depth > 1 时才可点。
什么时候该用哪种写法
| 场景 | 用哪个 API |
|---|---|
| 进入一个详情页 | `push(item / url, properties)` |
| 返回上一页 | `pop()` |
| 回到某个指定页面(如首页) | `pop(targetItem)` |
| 弹到只剩栈底首页 | `pop(null)` |
| 清空整个栈(含初始页) | `clear()` |
| 换掉当前页且不让用户回退 | `replace(item)` |
| 跳过动画、立即切换 | 加第三个参数 `StackView.Immediate` |
| 流程化进入多页(向导、多步表单) | `pushItems(items)` |
depth 贯穿所有场景:它是栈当前的真实高度,任何按钮要不要可用、栈空没空、该弹几层,都该问它,而不是自己维护计数器。
运行验证
- Qt Creator 打开
qml_stackview/CMakeLists.txt,按Ctrl+R运行; - 左侧点「基本操作」,连点 Push 叠加几页,观察底部 Depth 数字递增、Pop 按钮从置灰变为可点,再点 Replace 看栈深度不变但页面文字被替换;
- 点「批量操作」,分别点批量 Push、批量 Pop、清空栈,看 Depth 一次跳 5、跳 -5、回落到 1。
扩展复用方向
- 业务里"详情页→编辑页→确认页"这种强制流程,用
pushItems一次进栈,用户中途返回时按逆序逐层弹,天然保证不能乱跳; push的第二个参数是传参通道,往详情页带id、title这类数据时直接塞进 properties 对象,页面声明同名属性即可接收;- 需要"回到首页"按钮的页面,与其循环 pop,不如记录首页对象后
pop(home)一步到位,还自带从当前层回到栈底的转场动画。
已验证环境:
- Qt 版本:Qt 6.8.2 / Qt 6.11.1
- 操作系统:Windows 11
- GitHub:QML-Minimal-Demos/qml_stackview
把 StackView 的 API 按“进/退/换/批量”场景整理成对照表,depth 贯穿判断的思路很实用。适合做 QML 导航流程、向导表单的开发者当速查手册。