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++模型,支持更多字段。
适用场景:创意展示页、引导页、带曲线动效的导航菜单。
已验证环境:
- Qt 版本:Qt 6.8.2、Qt 6.11.1
- 操作系统:Windows 11
- GitHub:QML-Minimal-Demos/qml_pathview