QML StackView:实现卡片堆叠效果

StackView 的页面默认是"一次只显示一张",切页时旧的退场、新的进场。

但它的转场机制其实暴露了每个页面在栈里的位置信息,利用这一点可以做出一类很特别的效果:所有层级的页面同时可见,像一叠错开摆放的卡片,最上层是当前页,下面的每张卡露出一条边。

这篇拆解这个"卡片堆叠"demo:卡片为什么能叠着露出来、push/pop 时卡片怎么归位、以及三个让效果成立的细节。本示例借鉴了Qt官方的示例库。

堆叠效果

每推入一张新卡,它比下面那张往右下方错开 10 像素,并带淡入动画效果;最上层卡片中央有 Push/Pop 两个操作按钮,Push 叠一张带随机颜色的新卡(最多叠加 6 层),Pop 把最上面那张弹掉,下层卡片平滑归位。因为每张卡都一直可见,堆叠的层次感始终在画面上。

演示代码

qml 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

FadeInAnimation {
    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 20
        spacing: 15

        // ... 省略标题组件 TitleSeparator ...

        StackView {
            id: stack
            Layout.preferredWidth: 250
            Layout.preferredHeight: 200

            property real offset: 10

            initialItem: Component {
                id: page
                Rectangle {
                    property real pos: StackView.index * stack.offset
                    property real hue: Math.random()
                    color: Qt.hsla(hue, 0.5, 0.8, 0.6)
                    border.color: Qt.hsla(hue, 0.5, 0.5, 0.8)
                    StackView.visible: true

                    RowLayout {
                        anchors.centerIn: parent
                        spacing: 10

                        RoundButton {
                            text: "Push"
                            Layout.preferredWidth: 60
                            Layout.preferredHeight: 30
                            enabled: stack.depth < 6
                            onClicked: stack.push(page)
                        }

                        RoundButton {
                            text: "Pop"
                            Layout.preferredWidth: 60
                            Layout.preferredHeight: 30
                            enabled: stack.depth > 1
                            onClicked: stack.pop()
                        }
                    }
                }
            }

            pushEnter: Transition {
                id: pushEnter
                ParallelAnimation {
                    PropertyAction { property: "x"; value: pushEnter.ViewTransition.item.pos }
                    NumberAnimation { properties: "y"; from: pushEnter.ViewTransition.item.pos + stack.offset; to: pushEnter.ViewTransition.item.pos; duration: 400; easing.type: Easing.OutCubic }
                    NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 400; easing.type: Easing.OutCubic }
                }
            }
            popExit: Transition {
                id: popExit
                ParallelAnimation {
                    PropertyAction { property: "x"; value: popExit.ViewTransition.item.pos }
                    NumberAnimation { properties: "y"; from: popExit.ViewTransition.item.pos; to: popExit.ViewTransition.item.pos + stack.offset; duration: 400; easing.type: Easing.OutCubic }
                    NumberAnimation { property: "opacity"; from: 1; to: 0; duration: 400; easing.type: Easing.OutCubic }
                }
            }

            pushExit: Transition {
                id: pushExit
                PropertyAction { property: "x"; value: pushExit.ViewTransition.item.pos }
                PropertyAction { property: "y"; value: pushExit.ViewTransition.item.pos }
            }
            popEnter: Transition {
                id: popEnter
                PropertyAction { property: "x"; value: popEnter.ViewTransition.item.pos }
                PropertyAction { property: "y"; value: popEnter.ViewTransition.item.pos }
            }
        }

        Item { Layout.fillHeight: true }
    }
}

关键逻辑解析

每一层卡片都有自己的 "层号"

把 StackView 想成一叠扑克牌:最底下是第 0 层,往上依次是第 1、2、3 层。系统自动给每张卡标好层号,卡片被推进来或弹出去时,层号会自动更新,不用手动管。

demo 的错位量就是按层号算的:层号 × 10。于是第 0 层不动,第 1 层往右下挪 10 像素,第 2 层挪 20...... 一层层错开,堆叠效果就出来了。

"所有卡片都显示" 的开关

StackView 默认只显示最顶上那一张,其余层是不画的 ------ 这就是 "一次只看到一页" 的原因。demo 特意告诉每张卡:"不管你是不是最上层,都给我显示出来",下层卡的边缘才会露出来,形成错落堆叠。

⚠️ 做普通页面导航时千万别加这一句,否则切换页面时所有页面会叠在一起。

转场动画:一组 "在动",一组 "被带着走"

卡片进出场时,系统会告诉动画 "现在正在动的是哪张卡"。demo 把四种转场分成两组:

  • 负责 "动的卡" :新卡进来时,从下方 10 像素处缓缓滑到自己的位置,同时从透明变到不透明 ------ 看起来就像从下面那张卡的边缘滑出来、叠上去;弹走时反向滑出并淡出。
  • 负责 "被连带的卡" :新卡盖上来时下面的旧卡位置变了,旧卡弹走时露出来的下一张位置也变了。这些卡不需要动画,直接瞬间换到新位置 "站好" 就行 ------ 观众的注意力都在上面那张卡上。

(小提示:这两组动画写法里有个属性名是复数、有个是单数,都是对的,只是别写混。)

按钮固定在卡片正中

每张卡中央都放了一组 Push / Pop 按钮,让它始终停在卡片正中。如果换成 "铺满整卡" 的定位方式,按钮就会堆到左上角。虽然每层卡都有按钮,但只有最上面那张露脸,实际能点到的也只有它。

颜色随机、层数封顶

每张卡生成时随机取一个颜色(半透明粉彩,边框同色),每叠一层颜色都不同,谁上谁下一眼就能分清。另外还限制了层数:少于 6 层才允许继续叠,多于 1 层才允许弹 ------ 防止无限叠下去,或者把最底层弹掉。

为什么不能加裁剪(clip)

滑动类组件通常要裁剪边界,防止内容画出范围;但这里恰恰相反 ------ 堆叠效果靠的就是下层卡片露出的边缘。每张卡都比前一张往右下挪 10 像素,越靠上的卡越接近边界,一旦开了裁剪,露出的边缘被切掉,层次感就没了。


已验证环境

相关推荐
Quz1 小时前
QML Drawer 抽屉导航详解
qt
≮傷£≯√12 小时前
QGraphicsScene(一) drawCAD 问题
qt
qq_401700411 天前
Qt QUrl 详解与代码示例
开发语言·数据库·qt
雨田言炎1 天前
Qt的常用类QList--序列数据管理
嵌入式硬件·qt
朽棘不雕2 天前
Qt项目代码解释
开发语言·qt
CVer儿2 天前
vs2022配置qt
开发语言·qt
kaixin_learn_qt_ing2 天前
Qt 打开目录多选对话框
qt
lqj_本人2 天前
WinPcap 鸿蒙 PC 适配全记录:从 NPF 原始抓包到 VPN_TUN 与 libpcap 双通路
qt·华为·harmonyos
Quz2 天前
QML StackView:三种入栈方式(Item、Component 与 URL)
qt