不断涌出的爱意,使用SwiftUI搭建一个爱心粒子动画~

简介: 不断涌出的爱意,使用SwiftUI搭建一个爱心粒子动画~

不断涌出的爱意,使用SwiftUI搭建一个爱心粒子动画~


项目背景

近期某音上的粒子特效很火,一个中心点不断涌出各种颜色的粒子,形成一种炫彩缤纷的效果。

搜了网上很多资料,大多数都是AE的实现的动画,那在App上能不能实现这个效果呢?

说干就干。

项目搭建

首先,创建一个新的SwiftUI项目,命名为ParticleEffects

image.png

粒子运动

为了实现粒子动画效果,我们这里使用到的是GeometryEffect几何效果函数,它可以实现AnimatableViewModifier这两个协议,来模拟关键帧动画。

// 粒子动画
struct ParticleMotion: GeometryEffect {
    func effectValue(size: CGSize) -> ProjectionTransform {
        <#code#>
    }
}

上述代码是GeometryEffectSwiftUI中的应用,当我们创建一个结构体ParticleMotion遵循GeometryEffect协议时,SwiftUI会自动帮助我们补全该协议所涵盖的必要代码。

我们可以看到在ParticleMotion结构体中,SwiftUI补充了一个方法effectValue,它传入一个CGSize类型的size参数,返回ProjectionTransform投影变换效果。

在计算机图形学中,投影变换是把3D几何体转换成一种可作为二维图像渲染的方法。

然后我们声明几个必要变量来实现粒子运动动画,示例:

var time: Double
let v0: Double  = Double.random(in: 40...80)
let alpha: Double = Double.random(in: 0.0 ..< 2 * Double.pi)
let g = 15 * 9.81 
var animatableData: Double {
        get { time }
        set { time = newValue }
    }

粒子效果我们可以当作一种某一个视图随着重力场不断做布朗运动,简单来说就是做随机运动

因此我们声明了一个Double类型的参数time来作为时间,声明一个Double类型的常量v0作为粒子的随机初始速度,声明了一个Double类型的常量alpha作为粒子的随机投射角度,声明了一个常量g来作为重力,最后通过getset说明计算属性time是可读写的。

通过上面声明好的参数,我们来实现投影变换运动,示例:

func effectValue(size: CGSize) -> ProjectionTransform {
        let dx = v0 * time * cos(alpha)
        let dy = v0 * sin(alpha) * time - 0.5 * g * time * time
        let affineTransform = CGAffineTransform(translationX: CGFloat(dx), y: CGFloat(-dy))
        return ProjectionTransform(affineTransform)
    }

上述代码中使用到的数学公式这里简单说明下,我们使用投影变换中的affineTransform二维平面变换,这里的translationX参数代表X轴平移系数为【随机的初始速度*时间*随机投射角度余弦角】,Y轴平移系数为【随机初始速度*随机投射角度正弦角*时间-0.5倍重力*时间平方】。

粒子运动主要使用的原理是随机的连续位置形变,来模拟重力场下的布朗运动。

粒子效果

完成粒子运动方法后,我们来完成粒子视图,首先我们需要使用到ViewModifier属性修饰符来实现样式粒子效果,然后将ViewModifier属性修饰符赋予视图,示例:

//粒子视图
struct ParticleEffectView: ViewModifier {
    func body(content: Content) -> some View {
        <#code#>
    }
}

上述代码中,我们声明了一个结构体ParticleEffectView遵循ViewModifier协议。

然后创建了一个标准视图用来构建视图样式效果,我们还需要声明几个变量作为前期准备,示例:

let count: Int
let duration: Double = 2.0
@State var time: Double = 0.0

上述代码中,我们声明了一个Int类型常量count代表粒子数量,声明了一个Double类型的常量duration来代表粒子持续时间,声明了一个Double类型的变量time代表时间。

然后我们在粒子视图中构建样式,示例:

func body(content: Content) -> some View {
    let animation = Animation.linear(duration: duration).repeatForever(autoreverses: false)
    ZStack {
        ForEach(0 ..< count, id: \.self) { index in
            content
                .scaleEffect(CGFloat((duration - self.time) / duration))
                .modifier(ParticleMotion(time: self.time))
                .opacity((duration - self.time) / duration)
                .animation(animation.delay(Double.random(in: 0 ..< self.duration)))
                .blendMode(.plusLighter)
        }
        .onAppear {
            withAnimation {
                self.time = duration
            }
        }
    }
}

上述代码中,我们使用ForEach循环根据粒子数量count循环创建粒子,然后设置了粒子的scaleEffect缩放效果,设置粒子的modifier修饰为之前构建好的ParticleMotion粒子运动,使用opacity修饰符设置粒子的透明度,使用animation修饰符设置了粒子的延迟动画,使用blendMode修饰符设置了粒子的进行叠加图像计算。

粒子视图

完成上述准备后,我们来正式构建视图部分内容。示例:

struct ContentView: View {
    var body: some View {
        ZStack {
            Color.black
            Image(systemName: "heart.fill")
                .foregroundColor(Color.red)
                .font(.system(size: 60))
                .modifier(ParticleEffectView(count: 100))
            Image(systemName: "heart.fill")
                .foregroundColor(Color.green)
                .font(.system(size: 60))
                .modifier(ParticleEffectView(count: 100))
            Image(systemName: "heart.fill")
                .foregroundColor(Color.blue)
                .font(.system(size: 60))
                .modifier(ParticleEffectView(count: 100))
        }
        .edgesIgnoringSafeArea(.all)
    }
}

image.png

上述代码中,我们构建了3个Image图片,使用ZStack层叠视图包裹在一起,每一个Image图片都使用modifier修饰符使用ParticleEffectView粒子效果,最后整个背景颜色填充为黑色。

项目预览

image.png

恭喜你,完成了整个项目的全部内容!

快来动手试试吧。

相关文章
|
10月前
|
编解码 Java 计算机视觉
探索 JavaCV:开启计算机视觉与多媒体处理新世界
JavaCV 是基于 OpenCV 和 FFmpeg 的 Java 接口库,助力开发者实现视频处理、图像分析等功能。支持多种音视频格式编解码、GPU 加速及跨平台运行,适用于直播录制、摄像头捕获、美颜相机等场景,是多媒体开发的利器。
815 1
|
搜索推荐 安全 数据安全/隐私保护
构建高效网站后台会员管理系统:实战指南与代码示例
【7月更文挑战第5天】在当今的互联网时代,几乎每个网站或应用程序都需要一个强大的会员管理系统来维护用户信息、权限控制以及个性化体验。一个设计良好的会员管理系统不仅能够提升用户体验,还能增强数据安全性和运营效率。本文将深入探讨如何从零开始构建一个网站后台会员管理系统,涵盖系统设计思路、关键技术选型、功能模块实现,以及实战代码示例。
1557 3
|
存储 运维 安全
云上金融量化策略回测方案与最佳实践
【飞天技术沙龙—阿里云金融量化策略回测Workshop】在上海诺亚财富中心正式举行,汇聚多位行业专家,围绕量化投资的最佳实践、数据隐私安全、量化策略回测方案等议题进行深入探讨。
|
存储 JSON API
Python| 如何使用 DALL·E 和 OpenAI API 生成图像(1)
Python| 如何使用 DALL·E 和 OpenAI API 生成图像(1)
1022 7
Python| 如何使用 DALL·E 和 OpenAI API 生成图像(1)
|
存储 Linux 文件存储
常见的文件系统类型及其特点
常见的文件系统类型及其特点
|
存储 缓存
【顺序表和链表的对比】
【顺序表和链表的对比】
913 0
|
算法 计算机视觉 Docker
Docker容器中的OpenCV:轻松构建可移植的计算机视觉环境
Docker容器中的OpenCV:轻松构建可移植的计算机视觉环境
|
存储 NoSQL Java
Neo4j学习笔记(一) 安装配置
Neo4j学习笔记(一) 安装配置
1320 0
|
关系型数据库 MySQL Unix
nginx代理DB & ip限制
nginx代理DB & ip限制
|
JavaScript 数据库
使用 Webpack 打包 node 程序,node_modules 真的被干掉啦
网上很多关于 webpack 打包 node 的文章,但是他们都是只打包你写的 node.js 的代码, node_modules 直接都被排除了,这样处理的话如果发布到线上
2878 0