QML PathView 入门:直线路径与曲线路径

PathView 是 QML 里一个很有意思的视图组件,它跟 ListView 类似,也是 model + delegate 的结构,但区别在于项不是排成一条直线,而是沿着你定义的任意路径分布。想做卡片横向滑动、波浪式排列、旋转木马这类效果,PathView 都能搞定。

这篇文章用两个例子认识 PathView:第一个例子是最基础的直线路径;第二个例子把直线换成二次贝塞尔曲线,做出上下起伏的波浪效果。

基础直线路径

一排彩色卡片沿着水平直线排列,如图所示:

演示代码

qml 复制代码
import QtQuick
import QtQuick.Layouts

FadeInAnimation {
    id: root

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

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

        // 内容卡片
        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: "#fafafa"
            radius: 8
            border.color: "#e0e0e0"
            border.width: 1

            PathView {
                id: pathView
                anchors.fill: parent
                anchors.margins: 10
                clip: true
                focus: true

                model: ListModel {
                    ListElement { name: "音乐"; cardColor: "#e74c3c" }
                    ListElement { name: "电影"; cardColor: "#e67e22" }
                    ListElement { name: "读书"; cardColor: "#f1c40f" }
                    ListElement { name: "旅行"; cardColor: "#2ecc71" }
                    ListElement { name: "摄影"; cardColor: "#1abc9c" }
                    ListElement { name: "编程"; cardColor: "#3498db" }
                    ListElement { name: "运动"; cardColor: "#9b59b6" }
                    ListElement { name: "美食"; cardColor: "#e84393" }
                }

                pathItemCount: 5
                snapMode: PathView.SnapToItem

                preferredHighlightBegin: 0.5
                preferredHighlightEnd: 0.5
                highlightRangeMode: PathView.StrictlyEnforceRange

                path: Path {
                    startX: 0
                    startY: pathView.height / 2
                    PathLine {
                        x: pathView.width
                        y: pathView.height / 2
                    }
                }

                delegate: Rectangle {
                    id: card
                    required property int index
                    required property string name
                    required property string cardColor

                    width: 90
                    height: 120
                    radius: 8
                    color: cardColor
                    scale: PathView.isCurrentItem ? 1.1 : 0.85
                    opacity: PathView.isCurrentItem ? 1.0 : 0.55

                    Behavior on scale { NumberAnimation { duration: 150 } }
                    Behavior on opacity { NumberAnimation { duration: 150 } }

                    Column {
                        anchors.centerIn: parent
                        spacing: 4

                        Text {
                            anchors.horizontalCenter: parent.horizontalCenter
                            text: card.name
                            color: "white"
                            font.pointSize: 14
                            font.bold: true
                        }

                        Text {
                            anchors.horizontalCenter: parent.horizontalCenter
                            text: "index " + card.index
                            color: "#FFFFFFCC"
                            font.pointSize: 9
                        }
                    }
                }

                Keys.onLeftPressed: pathView.decrementCurrentIndex()
                Keys.onRightPressed: pathView.incrementCurrentIndex()
            }
        }

        RowLayout {
            Layout.fillWidth: true
            spacing: 10

            Text {
                text: "当前项:index %1(%2 / 共 %3 项)"
                      .arg(pathView.currentIndex)
                      .arg(pathView.model.get(pathView.currentIndex).name)
                      .arg(pathView.count)
                font.pointSize: 10
                color: "#666"
            }

            Item { Layout.fillWidth: true }

            Text {
                text: "拖动或使用 ← → 切换"
                font.pointSize: 10
                color: "#999"
            }
        }
    }
}

PathView包含三部分:model提供数据,delegate定义单项外观,path指定元素排布轨迹。

path为Path类型,起点是startX/startY,后续可拼接PathLine、PathQuad、PathCubic等路径元素。只用一条PathLine横向贯穿视图,y固定在视图垂直中点,卡片就水平居中排布。

pathItemCount控制一屏同时展示的元素数量,路径总长按该数量均分,以此确定元素间距。

设为5时,屏幕显示5张卡片。

将preferredHighlightBegin和preferredHighlightEnd设为相同值(例如0.5),搭配highlightRangeMode: PathView.StrictlyEnforceRange,选中项会固定在路径中点。拖动松开后,中间卡片即为选中项。

方向键切换直接调用PathView自带方法:decrementCurrentIndex()和incrementCurrentIndex()。

适用场景:横向卡片滑动、Banner 轮播、选项卡切换。

曲线路径

卡片沿着一条波浪形曲线排布,拖动底部滑块可以实时调整曲线的起伏程度,如图所示:

演示代码

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

FadeInAnimation {
    id: root

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

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

        // 内容卡片
        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: "#fafafa"
            radius: 8
            border.color: "#e0e0e0"
            border.width: 1

            PathView {
                id: pathView
                anchors.fill: parent
                anchors.margins: 10
                clip: true
                focus: true

                model: 10
                pathItemCount: 7
                snapMode: PathView.SnapToItem
                preferredHighlightBegin: 0.5
                preferredHighlightEnd: 0.5
                highlightRangeMode: PathView.StrictlyEnforceRange

                path: Path {
                    startX: 0
                    startY: pathView.height / 2
                    PathQuad {
                        x: pathView.width * 0.5
                        y: pathView.height / 2
                        controlX: pathView.width * 0.25
                        controlY: pathView.height * (0.5 - waveSlider.value)
                    }
                    PathQuad {
                        x: pathView.width
                        y: pathView.height / 2
                        controlX: pathView.width * 0.75
                        controlY: pathView.height * (0.5 + waveSlider.value)
                    }
                }

                delegate: Rectangle {
                    id: dot
                    required property int index

                    width: 64
                    height: 84
                    radius: 10
                    color: PathView.isCurrentItem ? "#1976D2" : "#90caf9"
                    scale: PathView.isCurrentItem ? 1.15 : 0.9

                    Behavior on color { ColorAnimation { duration: 150 } }
                    Behavior on scale { NumberAnimation { duration: 150 } }

                    Text {
                        anchors.centerIn: parent
                        text: dot.index + 1
                        color: PathView.isCurrentItem ? "white" : "#1565C0"
                        font.pointSize: 16
                        font.bold: true
                    }
                }

                Keys.onLeftPressed: pathView.decrementCurrentIndex()
                Keys.onRightPressed: pathView.incrementCurrentIndex()
            }
        }

        // 控制条:调整 PathQuad 的控制点位置
        RowLayout {
            Layout.fillWidth: true
            spacing: 10

            Text {
                text: "控制点偏移"
                font.pointSize: 10
                color: "#666"
            }

            Slider {
                id: waveSlider
                Layout.fillWidth: true
                from: 0.1
                to: 0.45
                value: 0.38
            }

            Text {
                Layout.preferredWidth: 36
                text: waveSlider.value.toFixed(2)
                font.pointSize: 10
                color: "#1976D2"
            }
        }
    }
}

曲线路径使用PathQuad,即二次贝塞尔曲线。一条二次贝塞尔曲线包含起点、终点和一个控制点。这里用两段PathQuad拼接成完整波浪:第一段从左侧到中间,控制点向上偏移;第二段从中间到右侧,控制点向下偏移,形成先上后下的S形波浪。

控制点Y坐标绑定滑块的value,拖动滑块可实时调整曲线弧度。滑块数值越大,控制点偏移量越大,波浪越陡;数值越小,曲线越接近直线。PathView会随路径变化自动重新排列所有元素,无需手动刷新。

model 传入数字代表共有N个条目,delegate仅能获取index,正式项目建议使用ListModel或C++模型,支持更多字段。

适用场景:创意展示页、引导页、带曲线动效的导航菜单。


已验证环境:

相关推荐
实心儿儿2 小时前
Qt — 音视频文件
qt·音视频
Cx330❀6 小时前
【Qt 进阶指南】详解 Qt 常用标准对话框与主窗口组件架构
开发语言·qt·ui·性能优化·架构·图形渲染
阿钱真强道7 小时前
25 嵌入式操作系统 | 设备面板:用 Qt 控件把“设备“画出来(本节不接串口)
开发语言·qt·flask·架构设计·协议契约
kaixin_learn_qt_ing8 小时前
使用 PySide6 编写Qt程序
qt
艾莉丝努力练剑9 小时前
【QT】系统相关:多线程QThread基础
开发语言·网络·c++·qt·学习·大模型
xcyxiner1 天前
DicomViewer24 修复编译bug(window test 失败)
前端·qt
sycmancia1 天前
Qml——Rectangle元素
qt
sycmancia1 天前
Qml——Item元素
qt
励志不掉头发的内向程序员1 天前
【从零写一个CAD 04】中键拖动平移:抓住一个点,让它一直待在鼠标底下
开发语言·c++·qt·学习·系统架构·计算机外设