SwiftUI 五彩纸屑动画
SwiftUI Confetti Animation
注意:我解决了这个问题。如果您对动画感兴趣,我用 SPM 创建了一个包,可以在 https://github.com/simibac/ConfettiSwiftUI
上找到
所以我正在尝试在 SwiftUI 中创建五彩纸屑动画。这是我目前所拥有的:
这一切都按预期工作,并使用以下代码完成:
struct Movement{
var x: CGFloat
var y: CGFloat
var z: CGFloat
var opacity: Double
}
struct FancyButtonViewModel: View {
@State var animate = [false]
@State var finishedAnimationCouter = 0
@State var counter = 0
var body: some View {
VStack{
ZStack{
ForEach(finishedAnimationCouter...counter, id:\.self){ i in
ConfettiContainer(animate:$animate[i], finishedAnimationCouter:$finishedAnimationCouter, num:1)
}
}
Button("Confetti"){
animate[counter].toggle()
animate.append(false)
counter+=1
}
}
}
}
struct ConfettiContainer: View {
@Binding var animate:Bool
@Binding var finishedAnimationCouter:Int
var num:Int
var body: some View{
ZStack{
ForEach(0...num-1, id:\.self){ _ in
Confetti(animate: $animate, finishedAnimationCouter:$finishedAnimationCouter)
}
}
.onChange(of: animate){_ in
DispatchQueue.main.asyncAfter(deadline: .now() + 3.5) {
finishedAnimationCouter+=1
}
}
}
}
struct Confetti: View{
@Binding var animate:Bool
@Binding var finishedAnimationCouter:Int
@State var movement = Movement(x: 0, y: 0, z: 1, opacity: 0)
var body: some View{
Text("❤️")
.frame(width: 50, height: 50, alignment: .center)
.offset(x: movement.x, y: movement.y)
.scaleEffect(movement.z)
.opacity(movement.opacity)
.onChange(of: animate) { _ in
withAnimation(Animation.easeOut(duration: 0.4)) {
movement.opacity = 1
movement.x = CGFloat.random(in: -150...150)
movement.y = -300 * CGFloat.random(in: 0.7...1)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
withAnimation(Animation.easeIn(duration: 3)) {
movement.y = 200
movement.opacity = 0.0
}
}
}
}
}
现在我想用动画五彩纸屑视图替换心形表情符号。因此,我创建了以下视图:
struct ConfettiView: View {
@State var animate = false
@State var xSpeed = Double.random(in: 0.7...2)
@State var zSpeed = Double.random(in: 1...2)
@State var anchor = CGFloat.random(in: 0...1).rounded()
var body: some View {
Rectangle()
.frame(width: 20, height: 20, alignment: .center)
.onAppear(perform: { animate = true })
.rotation3DEffect(.degrees(animate ? 360:0), axis: (x: 1, y: 0, z: 0))
.animation(Animation.linear(duration: xSpeed).repeatForever(autoreverses: false))
.rotation3DEffect(.degrees(animate ? 360:0), axis: (x: 0, y: 0, z: 1), anchor: UnitPoint(x: anchor, y: anchor))
.animation(Animation.linear(duration: zSpeed).repeatForever(autoreverses: false))
}
}
这也符合预期。但是,如果我将 Text("❤️")
替换为。 ConfettiView()
我得到了一些意想不到的动画,如下所示。 (我将五彩纸屑的数量减少到 1,这样可以更好地观察动画)。动画中断了什么?
你的动画重叠,要解决你需要加入状态值的内部动画。
这里是固定变体。使用 Xcode 12.1 / iOS 14.1
测试
struct ConfettiView: View {
@State var animate = false
@State var xSpeed = Double.random(in: 0.7...2)
@State var zSpeed = Double.random(in: 1...2)
@State var anchor = CGFloat.random(in: 0...1).rounded()
var body: some View {
Rectangle()
.frame(width: 20, height: 20, alignment: .center)
.onAppear(perform: { animate = true })
.rotation3DEffect(.degrees(animate ? 360:0), axis: (x: 1, y: 0, z: 0))
.animation(Animation.linear(duration: xSpeed).repeatForever(autoreverses: false), value: animate)
.rotation3DEffect(.degrees(animate ? 360:0), axis: (x: 0, y: 0, z: 1), anchor: UnitPoint(x: anchor, y: anchor))
.animation(Animation.linear(duration: zSpeed).repeatForever(autoreverses: false), value: animate)
}
}
注意:我解决了这个问题。如果您对动画感兴趣,我用 SPM 创建了一个包,可以在 https://github.com/simibac/ConfettiSwiftUI
上找到所以我正在尝试在 SwiftUI 中创建五彩纸屑动画。这是我目前所拥有的:
这一切都按预期工作,并使用以下代码完成:
struct Movement{
var x: CGFloat
var y: CGFloat
var z: CGFloat
var opacity: Double
}
struct FancyButtonViewModel: View {
@State var animate = [false]
@State var finishedAnimationCouter = 0
@State var counter = 0
var body: some View {
VStack{
ZStack{
ForEach(finishedAnimationCouter...counter, id:\.self){ i in
ConfettiContainer(animate:$animate[i], finishedAnimationCouter:$finishedAnimationCouter, num:1)
}
}
Button("Confetti"){
animate[counter].toggle()
animate.append(false)
counter+=1
}
}
}
}
struct ConfettiContainer: View {
@Binding var animate:Bool
@Binding var finishedAnimationCouter:Int
var num:Int
var body: some View{
ZStack{
ForEach(0...num-1, id:\.self){ _ in
Confetti(animate: $animate, finishedAnimationCouter:$finishedAnimationCouter)
}
}
.onChange(of: animate){_ in
DispatchQueue.main.asyncAfter(deadline: .now() + 3.5) {
finishedAnimationCouter+=1
}
}
}
}
struct Confetti: View{
@Binding var animate:Bool
@Binding var finishedAnimationCouter:Int
@State var movement = Movement(x: 0, y: 0, z: 1, opacity: 0)
var body: some View{
Text("❤️")
.frame(width: 50, height: 50, alignment: .center)
.offset(x: movement.x, y: movement.y)
.scaleEffect(movement.z)
.opacity(movement.opacity)
.onChange(of: animate) { _ in
withAnimation(Animation.easeOut(duration: 0.4)) {
movement.opacity = 1
movement.x = CGFloat.random(in: -150...150)
movement.y = -300 * CGFloat.random(in: 0.7...1)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
withAnimation(Animation.easeIn(duration: 3)) {
movement.y = 200
movement.opacity = 0.0
}
}
}
}
}
现在我想用动画五彩纸屑视图替换心形表情符号。因此,我创建了以下视图:
struct ConfettiView: View {
@State var animate = false
@State var xSpeed = Double.random(in: 0.7...2)
@State var zSpeed = Double.random(in: 1...2)
@State var anchor = CGFloat.random(in: 0...1).rounded()
var body: some View {
Rectangle()
.frame(width: 20, height: 20, alignment: .center)
.onAppear(perform: { animate = true })
.rotation3DEffect(.degrees(animate ? 360:0), axis: (x: 1, y: 0, z: 0))
.animation(Animation.linear(duration: xSpeed).repeatForever(autoreverses: false))
.rotation3DEffect(.degrees(animate ? 360:0), axis: (x: 0, y: 0, z: 1), anchor: UnitPoint(x: anchor, y: anchor))
.animation(Animation.linear(duration: zSpeed).repeatForever(autoreverses: false))
}
}
这也符合预期。但是,如果我将 Text("❤️")
替换为。 ConfettiView()
我得到了一些意想不到的动画,如下所示。 (我将五彩纸屑的数量减少到 1,这样可以更好地观察动画)。动画中断了什么?
你的动画重叠,要解决你需要加入状态值的内部动画。
这里是固定变体。使用 Xcode 12.1 / iOS 14.1
测试struct ConfettiView: View {
@State var animate = false
@State var xSpeed = Double.random(in: 0.7...2)
@State var zSpeed = Double.random(in: 1...2)
@State var anchor = CGFloat.random(in: 0...1).rounded()
var body: some View {
Rectangle()
.frame(width: 20, height: 20, alignment: .center)
.onAppear(perform: { animate = true })
.rotation3DEffect(.degrees(animate ? 360:0), axis: (x: 1, y: 0, z: 0))
.animation(Animation.linear(duration: xSpeed).repeatForever(autoreverses: false), value: animate)
.rotation3DEffect(.degrees(animate ? 360:0), axis: (x: 0, y: 0, z: 1), anchor: UnitPoint(x: anchor, y: anchor))
.animation(Animation.linear(duration: zSpeed).repeatForever(autoreverses: false), value: animate)
}
}