Qt Quick 粒子系统(一):架构总览与四层模型

目录

    • 一、为什么需要粒子系统
    • 二、开发环境与版本说明
    • 三、原理分析:四层架构
      • [3.1 ParticleSystem------容器与调度器](#3.1 ParticleSystem——容器与调度器)
      • [3.2 Emitter------发射器](#3.2 Emitter——发射器)
      • [3.3 ParticlePainter------渲染器](#3.3 ParticlePainter——渲染器)
      • [3.4 Affector------影响器](#3.4 Affector——影响器)
      • [3.5 粒子的完整生命周期](#3.5 粒子的完整生命周期)
    • [四、代码实现:Concept_ParticleSystem.qml 逐段解析](#四、代码实现:Concept_ParticleSystem.qml 逐段解析)
      • [4.1 完整代码](#4.1 完整代码)
      • [4.2 页面结构:BaseRect + PageTitle](#4.2 页面结构:BaseRect + PageTitle)
      • [4.3 粒子特效层](#4.3 粒子特效层)
      • [4.4 信息卡片层:ListModel + Repeater](#4.4 信息卡片层:ListModel + Repeater)
      • [4.5 入场动画:延迟渐入效果](#4.5 入场动画:延迟渐入效果)
      • [4.6 动画生命周期管理](#4.6 动画生命周期管理)
      • [4.7 文字渲染](#4.7 文字渲染)
      • [4.8 项目骨架:导航与页面管理](#4.8 项目骨架:导航与页面管理)
      • [4.9 通用组件复用](#4.9 通用组件复用)
    • 五、运行效果
    • 六、常见问题与边界条件
    • 七、总结与下篇预告

一、为什么需要粒子系统

在 QML 开发中,我们经常遇到这样的需求:游戏中的爆炸火花、天气应用的雪花飘落、音乐播放器的频谱背景、按钮点击后的光点扩散。这些效果有一个共同特征------大量微小元素各自独立运动,形成整体的视觉表现。

如果用传统的 QML 动画实现,每个元素都需要独立的 NumberAnimationBehavior,100 个元素就是 100 个动画对象,性能和代码量都会急剧恶化。而 Qt Quick 粒子系统模块QtQuick.Particles)正是为这类场景设计的:它在 GPU 层面批量管理成百上千个粒子,开发者只需声明发射规则和行为约束,系统自动处理生命周期、运动计算和渲染。

在正式学习之前,先对比三种常见方案的适用边界:

方案 适用场景 性能上限 开发复杂度
QML 动画(NumberAnimation 等) 单个元素的简单动效 <20个对象(仅供参考)
Canvas 手绘 自定义绘制逻辑 中等
ParticleSystem 模块 大量独立运动粒子 5000-10000粒子(仅供参考)
ShaderEffect GPU 全自定义 最高 很高

本文是系列第一篇,目标是建立 Qt Quick 粒子系统的全局认知框架------理解它的四层架构,知道每个组件的职责和协作关系,最终用 10 行代码写出一个最小粒子系统。

二、开发环境与版本说明

本文所有代码基于以下环境验证(验证日期:2026-06-05):

  • Qt 版本 :6.8.2(QtQuick.Particles 模块从 Qt 5 引入,Qt 6 中沿用,本文以 Qt 6 为准)
  • 编译器:MinGW 64-bit
  • 操作系统:Windows 11
  • 构建工具:CMake 3.29

如果你使用 Qt 6.5 及以上版本,代码无需修改即可运行。Qt 5.x 版本也可以使用粒子系统模块,但 import 路径和部分 API 存在差异(如 import QtQuick.Particles 2.15),本文统一以 Qt 6 为准。

项目的 CMake 配置中,关键部分如下:

cmake 复制代码
# 启用 QML 模块
qt_add_qml_module(appqml_particlesystem
    URI qml_particlesystem
    VERSION 1.0
    QML_FILES
        Main.qml
        Concept_ParticleSystem.qml
        # ... 其他 QML 文件
    RESOURCES res.qrc
)

粒子系统本身不需要额外的 C++ 代码,所有逻辑都在 QML 中完成。main.cpp 仅负责创建 QQmlApplicationEngine 并加载入口 QML 文件:

cpp 复制代码
QQmlApplicationEngine engine;
engine.loadFromModule("qml_particlesystem", "Main");

三、原理分析:四层架构

Qt Quick 粒子系统的设计可以抽象为四层,每层各司其职:
#mermaid-svg-STORCdF62sy2O4oq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-STORCdF62sy2O4oq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-STORCdF62sy2O4oq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-STORCdF62sy2O4oq .error-icon{fill:#552222;}#mermaid-svg-STORCdF62sy2O4oq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-STORCdF62sy2O4oq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-STORCdF62sy2O4oq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-STORCdF62sy2O4oq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-STORCdF62sy2O4oq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-STORCdF62sy2O4oq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-STORCdF62sy2O4oq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-STORCdF62sy2O4oq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-STORCdF62sy2O4oq .marker.cross{stroke:#333333;}#mermaid-svg-STORCdF62sy2O4oq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-STORCdF62sy2O4oq p{margin:0;}#mermaid-svg-STORCdF62sy2O4oq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-STORCdF62sy2O4oq .cluster-label text{fill:#333;}#mermaid-svg-STORCdF62sy2O4oq .cluster-label span{color:#333;}#mermaid-svg-STORCdF62sy2O4oq .cluster-label span p{background-color:transparent;}#mermaid-svg-STORCdF62sy2O4oq .label text,#mermaid-svg-STORCdF62sy2O4oq span{fill:#333;color:#333;}#mermaid-svg-STORCdF62sy2O4oq .node rect,#mermaid-svg-STORCdF62sy2O4oq .node circle,#mermaid-svg-STORCdF62sy2O4oq .node ellipse,#mermaid-svg-STORCdF62sy2O4oq .node polygon,#mermaid-svg-STORCdF62sy2O4oq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-STORCdF62sy2O4oq .rough-node .label text,#mermaid-svg-STORCdF62sy2O4oq .node .label text,#mermaid-svg-STORCdF62sy2O4oq .image-shape .label,#mermaid-svg-STORCdF62sy2O4oq .icon-shape .label{text-anchor:middle;}#mermaid-svg-STORCdF62sy2O4oq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-STORCdF62sy2O4oq .rough-node .label,#mermaid-svg-STORCdF62sy2O4oq .node .label,#mermaid-svg-STORCdF62sy2O4oq .image-shape .label,#mermaid-svg-STORCdF62sy2O4oq .icon-shape .label{text-align:center;}#mermaid-svg-STORCdF62sy2O4oq .node.clickable{cursor:pointer;}#mermaid-svg-STORCdF62sy2O4oq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-STORCdF62sy2O4oq .arrowheadPath{fill:#333333;}#mermaid-svg-STORCdF62sy2O4oq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-STORCdF62sy2O4oq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-STORCdF62sy2O4oq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-STORCdF62sy2O4oq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-STORCdF62sy2O4oq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-STORCdF62sy2O4oq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-STORCdF62sy2O4oq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-STORCdF62sy2O4oq .cluster text{fill:#333;}#mermaid-svg-STORCdF62sy2O4oq .cluster span{color:#333;}#mermaid-svg-STORCdF62sy2O4oq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-STORCdF62sy2O4oq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-STORCdF62sy2O4oq rect.text{fill:none;stroke-width:0;}#mermaid-svg-STORCdF62sy2O4oq .icon-shape,#mermaid-svg-STORCdF62sy2O4oq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-STORCdF62sy2O4oq .icon-shape p,#mermaid-svg-STORCdF62sy2O4oq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-STORCdF62sy2O4oq .icon-shape .label rect,#mermaid-svg-STORCdF62sy2O4oq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-STORCdF62sy2O4oq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-STORCdF62sy2O4oq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-STORCdF62sy2O4oq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ParticleSystem(容器/调度器)

管理所有粒子的生命周期、状态和全局调度
Emitter(发射器)

决定粒子从哪来、怎么来
ParticlePainter(渲染器)

决定粒子长什么样
Affector(影响器)

决定粒子发射后受什么力
ImageParticle

GPU 批量渲染
ItemParticle

QML 组件渲染

3.1 ParticleSystem------容器与调度器

ParticleSystem 是所有粒子元素的宿主容器。它本身不发射、不渲染、不影响粒子,而是负责:

  • 生命周期管理:跟踪每个粒子的诞生、运动和消亡
  • 全局调度:协调 Emitter、Painter、Affector 之间的数据流
  • 状态控制 :通过 runningpausedempty 等属性管理系统运行状态

一个粒子系统中可以包含多个 Emitter、多个 Painter 和多个 Affector,它们通过父子关系system 属性关联到同一个 ParticleSystem:

qml 复制代码
// 方式一:父子关系(推荐,简洁)
ParticleSystem {
    ImageParticle { ... }
    Emitter { ... }
}

// 方式二:显式 system 绑定(组件分散在不同层级时使用)
ParticleSystem { id: mySystem }
ImageParticle { system: mySystem }
Emitter { system: mySystem }

3.2 Emitter------发射器

Emitter 决定粒子从哪里来、以什么方式来。它的核心职责包括:

  • 发射区域 :通过 shape 属性限定粒子的出生位置(矩形、椭圆、线条等)
  • 发射频率emitRate 控制每秒发射的粒子数量
  • 初始属性lifeSpan(生命周期)、size(大小)、velocity(速度)、acceleration(加速度)
  • 运动方向 :粒子的扩散方向由 Direction 子组件控制,包括 AngleDirection(角度方向)、PointDirection(点方向)、TargetDirection(目标方向)等,后续文章将详细讲解
  • 粒子分组group 属性将发射的粒子归入指定组

一个 ParticleSystem 可以有多个 Emitter,分别发射不同样式的粒子。

3.3 ParticlePainter------渲染器

ParticlePainter 决定粒子长什么样。Qt Quick 提供了两种实现:

  • ImageParticle:使用图片作为粒子外观,GPU 批量渲染,性能最优。支持着色、旋转、变形、透明度等属性。
  • ItemParticle:使用任意 QML 组件作为粒子外观,灵活度最高,支持交互(点击、拖拽),但每个粒子是独立的 Item,性能较低。

两者通过 groups 属性指定负责渲染哪些粒子组,可以同时使用。

3.4 Affector------影响器

Affector 在粒子发射后、消亡前,对其属性进行二次修改。常见效果包括:

  • Gravity:重力加速度
  • Friction:速度衰减
  • Attractor:向目标点吸引
  • Wander:随机游走
  • Turbulence:湍流扰动

Affector 同样通过 groups 属性限定作用范围,可以叠加使用。

3.5 粒子的完整生命周期

将四层串联起来,一个粒子从诞生到消亡的完整流程是:
#mermaid-svg-eQ6AqyENRIhA8WWl{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eQ6AqyENRIhA8WWl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eQ6AqyENRIhA8WWl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eQ6AqyENRIhA8WWl .error-icon{fill:#552222;}#mermaid-svg-eQ6AqyENRIhA8WWl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eQ6AqyENRIhA8WWl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eQ6AqyENRIhA8WWl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eQ6AqyENRIhA8WWl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eQ6AqyENRIhA8WWl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eQ6AqyENRIhA8WWl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eQ6AqyENRIhA8WWl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eQ6AqyENRIhA8WWl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eQ6AqyENRIhA8WWl .marker.cross{stroke:#333333;}#mermaid-svg-eQ6AqyENRIhA8WWl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eQ6AqyENRIhA8WWl p{margin:0;}#mermaid-svg-eQ6AqyENRIhA8WWl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eQ6AqyENRIhA8WWl .cluster-label text{fill:#333;}#mermaid-svg-eQ6AqyENRIhA8WWl .cluster-label span{color:#333;}#mermaid-svg-eQ6AqyENRIhA8WWl .cluster-label span p{background-color:transparent;}#mermaid-svg-eQ6AqyENRIhA8WWl .label text,#mermaid-svg-eQ6AqyENRIhA8WWl span{fill:#333;color:#333;}#mermaid-svg-eQ6AqyENRIhA8WWl .node rect,#mermaid-svg-eQ6AqyENRIhA8WWl .node circle,#mermaid-svg-eQ6AqyENRIhA8WWl .node ellipse,#mermaid-svg-eQ6AqyENRIhA8WWl .node polygon,#mermaid-svg-eQ6AqyENRIhA8WWl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eQ6AqyENRIhA8WWl .rough-node .label text,#mermaid-svg-eQ6AqyENRIhA8WWl .node .label text,#mermaid-svg-eQ6AqyENRIhA8WWl .image-shape .label,#mermaid-svg-eQ6AqyENRIhA8WWl .icon-shape .label{text-anchor:middle;}#mermaid-svg-eQ6AqyENRIhA8WWl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eQ6AqyENRIhA8WWl .rough-node .label,#mermaid-svg-eQ6AqyENRIhA8WWl .node .label,#mermaid-svg-eQ6AqyENRIhA8WWl .image-shape .label,#mermaid-svg-eQ6AqyENRIhA8WWl .icon-shape .label{text-align:center;}#mermaid-svg-eQ6AqyENRIhA8WWl .node.clickable{cursor:pointer;}#mermaid-svg-eQ6AqyENRIhA8WWl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eQ6AqyENRIhA8WWl .arrowheadPath{fill:#333333;}#mermaid-svg-eQ6AqyENRIhA8WWl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eQ6AqyENRIhA8WWl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eQ6AqyENRIhA8WWl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eQ6AqyENRIhA8WWl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eQ6AqyENRIhA8WWl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eQ6AqyENRIhA8WWl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eQ6AqyENRIhA8WWl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eQ6AqyENRIhA8WWl .cluster text{fill:#333;}#mermaid-svg-eQ6AqyENRIhA8WWl .cluster span{color:#333;}#mermaid-svg-eQ6AqyENRIhA8WWl div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eQ6AqyENRIhA8WWl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eQ6AqyENRIhA8WWl rect.text{fill:none;stroke-width:0;}#mermaid-svg-eQ6AqyENRIhA8WWl .icon-shape,#mermaid-svg-eQ6AqyENRIhA8WWl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eQ6AqyENRIhA8WWl .icon-shape p,#mermaid-svg-eQ6AqyENRIhA8WWl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eQ6AqyENRIhA8WWl .icon-shape .label rect,#mermaid-svg-eQ6AqyENRIhA8WWl .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eQ6AqyENRIhA8WWl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eQ6AqyENRIhA8WWl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eQ6AqyENRIhA8WWl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Emitter 发射粒子

赋予初始位置、速度、大小、生命周期等属性
ParticleSystem 调度粒子运动

根据速度和加速度更新位置
Affector 修改粒子属性

施加重力、摩擦、湍流等力场
ParticlePainter 渲染粒子

根据当前属性绘制到屏幕
生命周期结束,粒子消亡

理解这个流程是后续学习的基础------Emitter 控制"从哪来",Direction 控制"往哪飞",Affector 控制"受什么力",Painter 控制"长什么样"。

四、代码实现:Concept_ParticleSystem.qml 逐段解析

项目中 Concept_ParticleSystem.qml 是第一个示例页面,它同时做了两件事:展示四层架构的知识卡片,以及用粒子特效作为背景。下面先看完整代码,再逐段解析关键设计。

4.1 完整代码

qml 复制代码
import QtQuick
import QtQuick.Particles
import QtQuick.Layouts
import "common"

BaseRect {
    id: root
    PageTitle { titleText: "ParticleSystem - 粒子系统" }

    // ── 粒子特效层 ──────────────────────────────────
    Rectangle {
        Layout.fillWidth: true
        Layout.fillHeight: true
        Layout.margins: 10
        color: "transparent"
        radius: 8

        ParticleSystem {
            id: particleSystem
            anchors.fill: parent
            running: root.isCurrentItem

            ImageParticle {
                source: "qrc:/images/star.png"
                alpha: 0.5
            }

            Emitter {
                anchors.centerIn: parent
                width: infoColumn.width
                height: infoColumn.height
                emitRate: 30
                lifeSpan: 3000
                size: 10
                sizeVariation: 10
            }

            Wander {
                system: particleSystem
                xVariance: 100
                yVariance: 100
                pace: 100
            }
        }

        // ── 信息卡片层 ──────────────────────────────
        ColumnLayout {
            id: infoColumn
            anchors.fill: parent
            anchors.margins: 15
            spacing: 15
            visible: false

            ListModel {
                id: infoModel
                ListElement {
                    title: "## ParticleSystem 是所有粒子元素的容器"
                    titleColor: "#4ECDC4"
                    content: "- 管理粒子生命周期、渲染和交互\n- 核心属性: running, paused, empty\n- 核心方法: start(), stop(), pause(), resume(), reset(), restart()"
                }
                ListElement {
                    title: "## Emitter - 发射器"
                    titleColor: "#FFE66D"
                    content: "- 发射逻辑粒子到 ParticleSystem\n- 核心属性: emitRate, lifeSpan, size, velocity, acceleration, shape, group\n- 核心方法: burst(count), pulse(duration)"
                }
                ListElement {
                    title: "## ParticlePainter - 渲染器"
                    titleColor: "#FF6B6B"
                    content: "- 指定如何绘制粒子\n- 子类: ImageParticle(图像渲染), ItemParticle(QML组件渲染)\n- 核心属性: groups, system"
                }
                ListElement {
                    title: "## Affector - 影响器"
                    titleColor: "#87CEEB"
                    content: "- 在粒子生命周期任意时刻修改其属性\n- 子类: Age, Attractor, Friction, Gravity, Turbulence, Wander, SpriteGoal\n- 核心属性: acceleration, velocity, position, relative"
                }
            }

            // ── 入场动画 Repeater ────────────────────
            Repeater {
                id: infoRepeater
                model: infoModel

                ColumnLayout {
                    id: delegateItem
                    spacing: 5
                    Layout.fillWidth: true

                    opacity: 0
                    transform: Translate { id: trans; y: 20 }

                    NumberAnimation {
                        id: entryAnim
                        target: delegateItem
                        property: "opacity"
                        from: 0; to: 1
                        duration: 400
                        easing.type: Easing.OutCubic
                    }
                    NumberAnimation {
                        id: slideAnim
                        target: trans
                        property: "y"
                        from: 20; to: 0
                        duration: 400
                        easing.type: Easing.OutCubic
                    }

                    Timer {
                        id: delayTimer
                        interval: 500 + index * 120
                        onTriggered: { entryAnim.start(); slideAnim.start() }
                    }

                    Connections {
                        target: root
                        function onIsCurrentItemChanged() {
                            if (root.isCurrentItem) {
                                delegateItem.opacity = 0
                                trans.y = 20
                                delayTimer.restart()
                            } else {
                                entryAnim.stop()
                                slideAnim.stop()
                                delayTimer.stop()
                            }
                        }
                    }

                    Component.onCompleted: {
                        if (root.isCurrentItem) delayTimer.start()
                    }

                    // ── 文字渲染 ──────────────────────
                    Text {
                        Layout.fillWidth: true
                        color: model.titleColor
                        text: model.title
                        textFormat: Text.MarkdownText
                        wrapMode: Text.WordWrap
                    }
                    Text {
                        Layout.fillWidth: true
                        color: "#aaa"
                        text: model.content
                        textFormat: Text.MarkdownText
                        wrapMode: Text.WordWrap
                    }

                    Rectangle {
                        Layout.fillWidth: true
                        Layout.topMargin: 5
                        height: 1
                        color: "#333"
                        visible: index < infoModel.count - 1
                    }
                }
            }

            Item { Layout.fillHeight: true }
        }
    }
}

4.2 页面结构:BaseRect + PageTitle

页面继承 BaseRect 而非直接使用 RectangleBaseRect 提供了两个关键能力:

  • 自动布局 :内部使用 ColumnLayout,子元素自动纵向排列
  • 页面激活感知 :暴露 isCurrentItem 属性,由 StackLayout 自动赋值

PageTitle 是通用标题组件,18px 白色加粗居中显示。

4.3 粒子特效层

粒子特效层的核心是 ParticleSystem,包含三个子组件:

running: root.isCurrentItem ------这是整个项目的核心模式。当用户切换到其他页面时,StackLayout 会将 isCurrentItem 设为 false,粒子系统自动停止,避免不可见时浪费 GPU。切换回来时自动恢复。

Emitter 尺寸绑定 infoColumn ------发射区域不是固定大小,而是绑定到下方文字区域的尺寸。这样粒子从文字区域的范围内涌出,形成"文字背后有粒子"的视觉效果。这是一个常见的设计技巧:让发射区域跟随内容自适应。

alpha: 0.5 ------粒子半透明,避免遮挡前景文字。粒子系统和 UI 内容共存时,透明度控制是关键。

Wander 随机漂移 ------粒子不仅从中心扩散,还带有布朗运动般的漂移效果。xVariance: 100yVariance: 100 控制漂移幅度,pace: 100 控制漂移频率。

4.4 信息卡片层:ListModel + Repeater

ColumnLayoutinfoColumn)设为 visible: false,它本身不显示------只为 Emitter 提供尺寸参考(width: infoColumn.width)。实际的文字显示由 Repeater 的 delegate 完成。

ListModel 定义了四层架构的描述数据,每条包含标题颜色和内容。Repeater 为每条数据生成一个 ColumnLayout delegate,包含标题 Text、内容 Text 和分割线 Rectangle。

4.5 入场动画:延迟渐入效果

每张卡片都有一个精心设计的入场动画,分三个阶段:

  1. 延迟Timerinterval: 500 + index * 120,第一张卡片延迟 500ms,第二张 620ms,第三张 740ms......形成逐张依次出现的效果
  2. 渐入entryAnimopacity 从 0 动画到 1
  3. 上滑slideAnimTranslate.y 从 20 动画到 0,卡片从下方滑入

Easing.OutCubic 缓动曲线让动画有"快速进入、缓慢停下"的质感,比线性动画更自然。

4.6 动画生命周期管理

动画必须和页面激活状态同步。Connections 监听 root.isCurrentItemChanged 信号:

  • 页面激活时:重置 opacity 和位置,重启延迟 Timer,动画从头播放
  • 页面离开时:停止所有动画和 Timer,避免后台无意义执行

Component.onCompleted 处理首次加载的边界情况------如果页面在组件创建时就已经是当前页(比如应用启动时默认显示第一页),直接启动动画。

4.7 文字渲染

Text.MarkdownText 让标题中的 ## 语法被正确渲染为二级标题样式。每张卡片之间用 1px 的深灰分割线隔开,最后一张卡片不显示分割线(visible: index < infoModel.count - 1)。

4.8 项目骨架:导航与页面管理

所有示例页面都在 Main.qml 中通过 ListView + StackLayout 组织:

qml 复制代码
Window {
    RowLayout {
        ListView {
            id: navList
            Layout.preferredWidth: 180
            model: ListModel {
                ListElement { name: "系统概述"; category: "基本概念" }
                ListElement { name: "系统控制"; category: "基本概念" }
                // ... 更多页面
            }
        }
        StackLayout {
            id: contentStack
            Concept_ParticleSystem {}     // 第 0 页
            Concept_SystemControl {}      // 第 1 页
            // ... 更多页面
        }
    }
}

ListView 提供左侧分类导航,StackLayout 管理右侧内容区。点击导航项时 contentStack.currentIndex 切换,BaseRect 的 isCurrentItem 自动跟随变化,粒子系统和动画的启停全部自动化------开发者在每个页面中只需声明 running: root.isCurrentItem 即可。

4.9 通用组件复用

项目定义了几个通用组件,保证页面风格一致:

组件 文件 职责
BaseRect common/BaseRect.qml 页面基础容器,管理 isCurrentItem
PageTitle common/PageTitle.qml 页面标题,18px 白色加粗居中
BottomNote common/BottomNote.qml 底部说明文字,深灰背景
SubTitleRow common/SubTitleRow.qml 小节副标题
ColorButton common/ColorButton.qml 彩色按钮组件

这些组件将布局和样式从业务逻辑中抽离,让每个示例页面只需关注粒子系统本身的代码。

五、运行效果

运行项目后,主界面如下:

  • 左侧:180px 宽的导航栏,按分类分组(基本概念、发射区域、扩散方向、粒子随机性、粒子影响器、尾迹发射器、粒子系统示例)------规划分类,目前还在做
  • 右侧:StackLayout 内容区,显示当前选中的粒子示例
  • 粒子行为:切换页面时,当前页的粒子系统自动运行,离开时自动停止

点击「系统概述」进入第一个示例页面,可以看到四层架构的概览信息和背景粒子效果:

运行截图说明:页面展示 ParticleSystem / Emitter / ParticlePainter / Affector 四层组件的职责说明,背景有 Wander 随机漂移的星形粒子。

项目规划有 7 个分类、30+ 个示例页面,覆盖了 Qt Quick 粒子系统的完整功能。后续文章将逐个模块深入讲解。

六、常见问题与边界条件

Q:Qt 5 项目能否使用粒子系统?

可以。Qt 5 的 QtQuick.Particles 模块功能基本一致,但 import 语句需要指定版本号:

qml 复制代码
// Qt 5 写法
import QtQuick.Particles 2.15

// Qt 6 写法(推荐)
import QtQuick.Particles

Q:ParticleSystem 和 QML 的 Behavior/NumberAnimation 动画有什么区别?

BehaviorNumberAnimation 适合单个元素的属性动画(如按钮缩放、颜色渐变)。ParticleSystem 适合大量独立运动对象的场景------它在引擎层面批量处理粒子数据,避免了为每个粒子创建独立动画对象的开销。

简单判断标准:如果动画对象超过 20 个,考虑 ParticleSystem。

Q:粒子系统的性能上限是多少?

取决于设备 GPU 能力、粒子大小和渲染方式。使用 ImageParticle(GPU 批量渲染)时,中端设备通常可支持数千个活跃粒子。使用 ItemParticle(QML 组件渲染)时,由于每个粒子都是独立的 QML Item,建议控制在 100 个以内。

性能优化的关键手段:

  • 不可见时停止粒子系统(running: isCurrentItem
  • 使用 maximumEmitted 限制最大活跃粒子数
  • 优先使用 ImageParticle 而非 ItemParticle

Q:粒子系统的 running 属性默认是什么?

默认为 true。如果在页面切换场景中使用,务必绑定为 false 或绑定到页面可见性属性,避免不可见时仍消耗 GPU 资源。

七、总结与下篇预告

本文建立了 Qt Quick 粒子系统的四层架构认知:

  • ParticleSystem 是容器和调度器,管理全局生命周期
  • Emitter 是发射器,决定粒子从哪来、怎么来
  • ParticlePainter 是渲染器,决定粒子长什么样(ImageParticle 批量渲染 / ItemParticle 组件渲染)
  • Affector 是影响器,决定粒子发射后受什么力

记住这个四层模型:容器、发、画、改------容器管理一切,发射器创造粒子,渲染器绘制粒子,影响器改造粒子。

下一篇将深入 ParticleSystem 的状态控制,详细讲解 start() / stop() / pause() / resume() / reset() / restart() 六种方法的行为差异,以及 running / paused / empty 三态模型的工作机制。


资源下载:qml_particlesystem ------ 包含完整的、可运行的代码

系列目录

  • 本文:Qt Quick 粒子系统(一):架构总览与四层模型
  • 下一篇:Qt Quick 粒子系统(二):系统控制与生命周期管理
相关推荐
一尘之中9 小时前
基于架构的软件开发方法
学习·架构·ai写作
AI科技星9 小时前
第三卷:质数王朝志 第四章:RSA护国玄阵,质数锁天地,一数镇万法
android·人工智能·架构·概率论·学习方法
陈猪的杰咪9 小时前
【2026最新指南】AI大模型API中转站选型参考:国内稳定接入ChatGPT、Claude、Gemini等主流模型实践分享
运维·网络·人工智能·chatgpt·架构
小雨下雨的雨10 小时前
HarmonyOS V2状态管理深度解析:列表数据与分页架构
华为·架构·harmonyos·鸿蒙
Ztopcloud极拓云视角12 小时前
ChatGPT超级应用改版技术解析:Codex集成架构与多模型路由实战
人工智能·chatgpt·架构
逻极1 天前
Hermes Agent深度探索:一个会自我沉淀经验的终端智能体
架构·llm·agent·rag·多智能体系统·hermes agent·hermes
数智顾问1 天前
(151页PPT)XX集团信息化整体架构规划及ERP方案建议书(附下载方式)
大数据·架构
数据法师1 天前
QuickSay :基于 Qt 的轻量级快捷短语管理工具
开发语言·qt